@goweekdays/core 2.11.10 → 2.11.12
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 +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +143 -140
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
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
|
-
}
|
|
933
|
+
}) => Promise<{
|
|
934
934
|
monthlyAmount: number;
|
|
935
935
|
proratedAmount: number;
|
|
936
936
|
subscriptionAmount: number;
|
package/dist/index.js
CHANGED
|
@@ -6020,7 +6020,7 @@ var schemaSubscriptionTransaction = import_joi22.default.object({
|
|
|
6020
6020
|
"promo-expired",
|
|
6021
6021
|
"promo-updated"
|
|
6022
6022
|
).required(),
|
|
6023
|
-
description: import_joi22.default.string().
|
|
6023
|
+
description: import_joi22.default.string().optional().allow("", null),
|
|
6024
6024
|
createdBy: import_joi22.default.string().hex().length(24).required(),
|
|
6025
6025
|
createdByName: import_joi22.default.string().optional().allow("", null)
|
|
6026
6026
|
});
|
|
@@ -8417,147 +8417,114 @@ function useSubscriptionService() {
|
|
|
8417
8417
|
}
|
|
8418
8418
|
return total;
|
|
8419
8419
|
}
|
|
8420
|
-
|
|
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(0, seats - limit) * 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 computeAdditionalSeatAmount(additionalSeats, planPrice, promo, paidSeats) {
|
|
8436
|
+
if (additionalSeats <= 0)
|
|
8437
|
+
return 0;
|
|
8438
|
+
if (!promo) {
|
|
8439
|
+
return additionalSeats * planPrice;
|
|
8440
|
+
}
|
|
8441
|
+
const limit = promo.seats && promo.seats > 0 ? promo.seats : Infinity;
|
|
8442
|
+
switch (promo.type) {
|
|
8443
|
+
case "flat": {
|
|
8444
|
+
const chargeableSeats = Math.max(0, paidSeats + additionalSeats - limit) - Math.max(0, paidSeats - limit);
|
|
8445
|
+
return chargeableSeats * planPrice;
|
|
8446
|
+
}
|
|
8447
|
+
case "fixed": {
|
|
8448
|
+
const promoSeatsLeft = Math.max(0, limit - paidSeats);
|
|
8449
|
+
const promoSeats = Math.min(additionalSeats, promoSeatsLeft);
|
|
8450
|
+
const normalSeats = additionalSeats - promoSeats;
|
|
8451
|
+
return promoSeats * (promo.fixedRate ?? 0) + normalSeats * planPrice;
|
|
8452
|
+
}
|
|
8453
|
+
case "volume":
|
|
8454
|
+
return promo.tiers?.length ? calculateVolumeTierAmount(
|
|
8455
|
+
promo.tiers,
|
|
8456
|
+
paidSeats + 1,
|
|
8457
|
+
additionalSeats,
|
|
8458
|
+
planPrice
|
|
8459
|
+
) : additionalSeats * planPrice;
|
|
8460
|
+
default:
|
|
8461
|
+
return additionalSeats * planPrice;
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
function computeProration(subscription, seats, plan, promo) {
|
|
8465
|
+
const additionalSeats = Math.max(0, seats - subscription.paidSeats);
|
|
8466
|
+
if (additionalSeats === 0)
|
|
8467
|
+
return 0;
|
|
8468
|
+
const today = /* @__PURE__ */ new Date();
|
|
8469
|
+
const nextBilling = new Date(subscription.nextBillingDate);
|
|
8470
|
+
const daysRemaining = Math.max(
|
|
8471
|
+
0,
|
|
8472
|
+
(nextBilling.getTime() - today.getTime()) / 864e5
|
|
8473
|
+
);
|
|
8474
|
+
const cycleDays = plan.billingCycle === "yearly" ? 365 : 30;
|
|
8475
|
+
if (daysRemaining === 0)
|
|
8476
|
+
return 0;
|
|
8477
|
+
const additionalAmount = computeAdditionalSeatAmount(
|
|
8478
|
+
additionalSeats,
|
|
8479
|
+
plan.price,
|
|
8480
|
+
promo,
|
|
8481
|
+
subscription.paidSeats
|
|
8482
|
+
);
|
|
8483
|
+
return additionalAmount / cycleDays * daysRemaining;
|
|
8484
|
+
}
|
|
8485
|
+
async function computeFee(value) {
|
|
8421
8486
|
const { error } = schemaSubscriptionCompute.validate(value);
|
|
8422
8487
|
if (error) {
|
|
8423
8488
|
throw new import_utils44.BadRequestError(`Invalid subscription data: ${error.message}`);
|
|
8424
8489
|
}
|
|
8425
8490
|
const existingSubscription = await getByOrg(value.org);
|
|
8426
8491
|
const plan = await getPlanById(value.plan);
|
|
8427
|
-
if (!plan)
|
|
8492
|
+
if (!plan)
|
|
8428
8493
|
throw new import_utils44.BadRequestError("Plan not found.");
|
|
8494
|
+
const isNew = !existingSubscription;
|
|
8495
|
+
const isPromoChange = !!existingSubscription && value.promoCode !== void 0 && value.promoCode !== existingSubscription.promoCode;
|
|
8496
|
+
const isSeatIncrease = !!existingSubscription && value.seats > existingSubscription.seats;
|
|
8497
|
+
if (isPromoChange && isSeatIncrease) {
|
|
8498
|
+
throw new import_utils44.BadRequestError(
|
|
8499
|
+
"Cannot change promo code while increasing seats. Perform actions separately."
|
|
8500
|
+
);
|
|
8429
8501
|
}
|
|
8430
|
-
const
|
|
8431
|
-
|
|
8432
|
-
|
|
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
|
-
}
|
|
8502
|
+
const effectivePromoCode = isPromoChange ? value.promoCode : existingSubscription?.promoCode;
|
|
8503
|
+
const promo = effectivePromoCode ? await getPromoByCode(effectivePromoCode) : null;
|
|
8504
|
+
const monthlyAmount = computeMonthlyAmount(value.seats, plan.price, promo);
|
|
8483
8505
|
let proratedAmount = 0;
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
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
|
|
8506
|
+
if (existingSubscription && isSeatIncrease && !isPromoChange) {
|
|
8507
|
+
proratedAmount = computeProration(
|
|
8508
|
+
existingSubscription,
|
|
8509
|
+
value.seats,
|
|
8510
|
+
plan,
|
|
8511
|
+
promo
|
|
8499
8512
|
);
|
|
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
8513
|
}
|
|
8543
|
-
const isIncrease = existingSubscription && value.seats > existingSubscription.paidSeats;
|
|
8544
8514
|
let subscriptionAmount;
|
|
8545
|
-
if (
|
|
8546
|
-
subscriptionAmount =
|
|
8547
|
-
} else if (
|
|
8548
|
-
subscriptionAmount =
|
|
8549
|
-
} else if (
|
|
8550
|
-
subscriptionAmount =
|
|
8515
|
+
if (isNew) {
|
|
8516
|
+
subscriptionAmount = monthlyAmount;
|
|
8517
|
+
} else if (isPromoChange) {
|
|
8518
|
+
subscriptionAmount = monthlyAmount;
|
|
8519
|
+
} else if (isSeatIncrease) {
|
|
8520
|
+
subscriptionAmount = existingSubscription.amount + proratedAmount;
|
|
8551
8521
|
} else {
|
|
8552
|
-
subscriptionAmount =
|
|
8522
|
+
subscriptionAmount = existingSubscription.amount;
|
|
8553
8523
|
}
|
|
8554
8524
|
return {
|
|
8555
|
-
// The monthly fee for the next billing cycle
|
|
8556
8525
|
monthlyAmount: Math.round(monthlyAmount * 100) / 100,
|
|
8557
|
-
// The prorated amount to charge now for mid-cycle seat additions
|
|
8558
8526
|
proratedAmount: Math.round(proratedAmount * 100) / 100,
|
|
8559
|
-
|
|
8560
|
-
subscriptionAmount,
|
|
8527
|
+
subscriptionAmount: Math.round(subscriptionAmount * 100) / 100,
|
|
8561
8528
|
currency: plan.currency
|
|
8562
8529
|
};
|
|
8563
8530
|
}
|
|
@@ -8699,20 +8666,23 @@ function useSubscriptionService() {
|
|
|
8699
8666
|
const { subscriptionAmount, proratedAmount, currency } = await computeFee(
|
|
8700
8667
|
{
|
|
8701
8668
|
seats: value.seats,
|
|
8702
|
-
promoCode: value.promoCode ?? "",
|
|
8703
8669
|
plan: value.plan ?? "",
|
|
8704
8670
|
org: value.org
|
|
8705
8671
|
}
|
|
8706
8672
|
);
|
|
8707
8673
|
const seatIncreased = value.seats > subscription.paidSeats;
|
|
8708
|
-
const additionalSeats = value.seats - subscription.paidSeats;
|
|
8709
|
-
const paidSeats = seatIncreased ?
|
|
8674
|
+
const additionalSeats = seatIncreased ? value.seats - subscription.paidSeats : 0;
|
|
8675
|
+
const paidSeats = seatIncreased ? subscription.paidSeats + additionalSeats : subscription.paidSeats;
|
|
8710
8676
|
await updateById(
|
|
8711
8677
|
subscription._id?.toString() ?? "",
|
|
8712
|
-
{
|
|
8678
|
+
{
|
|
8679
|
+
seats: value.seats,
|
|
8680
|
+
amount: subscriptionAmount,
|
|
8681
|
+
paidSeats
|
|
8682
|
+
},
|
|
8713
8683
|
session
|
|
8714
8684
|
);
|
|
8715
|
-
if (seatIncreased) {
|
|
8685
|
+
if (seatIncreased && proratedAmount > 0) {
|
|
8716
8686
|
await addTransaction(
|
|
8717
8687
|
{
|
|
8718
8688
|
type: "add-seat",
|
|
@@ -8778,15 +8748,12 @@ function useSubscriptionService() {
|
|
|
8778
8748
|
if (!plan) {
|
|
8779
8749
|
throw new import_utils44.BadRequestError("Plan not found.");
|
|
8780
8750
|
}
|
|
8781
|
-
const { subscriptionAmount, currency } = await computeFee(
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
},
|
|
8788
|
-
true
|
|
8789
|
-
);
|
|
8751
|
+
const { subscriptionAmount, currency } = await computeFee({
|
|
8752
|
+
seats: subscription.seats,
|
|
8753
|
+
promoCode: value.promoCode ?? "",
|
|
8754
|
+
plan: plan._id?.toString() ?? "",
|
|
8755
|
+
org: value.org
|
|
8756
|
+
});
|
|
8790
8757
|
await updateById(
|
|
8791
8758
|
subscription._id?.toString() ?? "",
|
|
8792
8759
|
{ promoCode: value.promoCode ?? "", amount: subscriptionAmount },
|
|
@@ -8795,6 +8762,9 @@ function useSubscriptionService() {
|
|
|
8795
8762
|
if (subscription.promoCode) {
|
|
8796
8763
|
await updatePromoUsageStatusByOrgId(value.org, "inactive", session);
|
|
8797
8764
|
}
|
|
8765
|
+
let description = `Promo code updated to ${value.promoCode ?? "none"}. At the time of update, ${(0, import_utils44.formatNumber)(subscription.paidSeats, {
|
|
8766
|
+
decimalPlaces: 0
|
|
8767
|
+
})} seat(s) were already included in the subscription.`;
|
|
8798
8768
|
if (promo && promo._id) {
|
|
8799
8769
|
await addPromoUsage(
|
|
8800
8770
|
{
|
|
@@ -8804,11 +8774,44 @@ function useSubscriptionService() {
|
|
|
8804
8774
|
},
|
|
8805
8775
|
session
|
|
8806
8776
|
);
|
|
8777
|
+
const additionalDescription = `${promo.seats ? `Seats beyond ${(0, import_utils44.formatNumber)(promo.seats, {
|
|
8778
|
+
decimalPlaces: 0
|
|
8779
|
+
})} are charged at the standard rate.` : ""}`;
|
|
8780
|
+
if (promo.type === "flat") {
|
|
8781
|
+
description += ` ${(0, import_utils44.formatNumber)(promo.flatRate ?? 0, {
|
|
8782
|
+
currency,
|
|
8783
|
+
useSymbol: true
|
|
8784
|
+
})} ${promo.seats ? `limited to ${(0, import_utils44.formatNumber)(promo.seats, {
|
|
8785
|
+
decimalPlaces: 0
|
|
8786
|
+
})} seat(s)` : " for all seats"}. ${additionalDescription}`;
|
|
8787
|
+
}
|
|
8788
|
+
if (promo.type === "fixed") {
|
|
8789
|
+
description += ` ${(0, import_utils44.formatNumber)(promo.fixedRate ?? 0, {
|
|
8790
|
+
currency,
|
|
8791
|
+
useSymbol: true
|
|
8792
|
+
})} per seat${promo.seats ? `, limited to ${(0, import_utils44.formatNumber)(promo.seats, {
|
|
8793
|
+
decimalPlaces: 0
|
|
8794
|
+
})} seat(s)` : ""}. ${additionalDescription}`;
|
|
8795
|
+
}
|
|
8796
|
+
if (promo.type === "volume") {
|
|
8797
|
+
if (promo.tiers && promo.tiers.length > 0) {
|
|
8798
|
+
const tierDescriptions = promo.tiers.map((tier) => {
|
|
8799
|
+
const maxSeatsDesc = tier.maxSeats === 0 ? "and above" : `to ${(0, import_utils44.formatNumber)(tier.maxSeats, { decimalPlaces: 0 })}`;
|
|
8800
|
+
return `${(0, import_utils44.formatNumber)(tier.minSeats, {
|
|
8801
|
+
decimalPlaces: 0
|
|
8802
|
+
})} ${maxSeatsDesc} ${(0, import_utils44.formatNumber)(tier.rate, {
|
|
8803
|
+
currency,
|
|
8804
|
+
useSymbol: true
|
|
8805
|
+
})} per seat`;
|
|
8806
|
+
}).join(", ");
|
|
8807
|
+
description += ` Volume tiers, ${tierDescriptions}.`;
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8807
8810
|
}
|
|
8808
8811
|
await addTransaction(
|
|
8809
8812
|
{
|
|
8810
8813
|
type: "promo-updated",
|
|
8811
|
-
description
|
|
8814
|
+
description,
|
|
8812
8815
|
amount: 0,
|
|
8813
8816
|
currency,
|
|
8814
8817
|
subscription: subscription._id?.toString() ?? "",
|