Files
cfw-autumn/server/tests/utils/init.ts
amianthus 5cb46bba96 Squashed commit of the following:
commit da2693567c
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 14:45:25 2025 +0100

    chore: ran biome format

commit db19928cea
Merge: 3005d452 d5d285a2
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:25:11 2025 +0100

    Merge branch 'staging' of https://github.com/useautumn/autumn into staging

commit 3005d45252
Merge: 5cb89756 784f7f92
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:25:01 2025 +0100

    Merge branch 'main' into staging

commit d5d285a25b
Merge: 894f943f b3952a27
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 13:19:48 2025 +0100

    Merge pull request #224 from muhammadusmandev/fix/create-product-prevent-empty-name-submission

    Fix: Create product prevent API call if product name field is empty and invalid characters

commit 894f943f31
Merge: 5cb89756 59aaf3c4
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 13:19:15 2025 +0100

    Merge pull request #223 from sidgaikwad/Fixes/Update-logic-reward-program

    Fixes/update logic reward program

commit 59aaf3c430
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:17:56 2025 +0100

    Fixes/Update-logic-reward-program

commit 5f9eb4f02d
Merge: 7b1b2cd2 5cb89756
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:17:46 2025 +0100

    Merge branch 'staging' into Fixes/Update-logic-reward-program

commit 7b1b2cd278
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:12:39 2025 +0100

    fix: show error message for update reward program

commit 5cb897560d
Merge: 79cf0f58 94dd5ba5
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:06:11 2025 +0100

    Merge branch 'staging' of https://github.com/useautumn/autumn into staging

commit 79cf0f5839
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 13:06:09 2025 +0100

    fix: update feature modal scrolls

commit 94dd5ba5b1
Merge: 9715e04e 609bd24b
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 12:58:51 2025 +0100

    Merge pull request #215 from shreyash1231/fix/Disabled-Create-Customer-Button-Until-Id-Or-Email-is-Filled-To-Prevent-API-CALL

    Fix/disabled create customer button until id or email is filled to prevent api call

commit 9715e04edd
Merge: 97190711 fe96c87a
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 12:44:40 2025 +0100

    Merge pull request #213 from SirTenzin/fix/event-timezone

    fix: 🐛 timezones not localised

commit 9719071149
Merge: 9c1862b4 7e201dae
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 12:44:28 2025 +0100

    Merge pull request #217 from SirTenzin/fix/trmnl-values

    fix: 🐛 only counted row instead of value

commit 9c1862b405
Merge: 6040c103 4fd25cc1
Author: John Yeo <51376134+johnyeocx@users.noreply.github.com>
Date:   Mon Sep 22 12:43:36 2025 +0100

    Merge pull request #216 from SirTenzin/feat/reward-migrations

    fix: 🐛 reward price_ids migrations

commit 4fd25cc161
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 12:43:06 2025 +0100

    fix: ent rollover null / undefined difference

commit 784f7f9290
Merge: dd0e9f1f 6040c103
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 10:40:49 2025 +0100

    fix: referral program coupon reward, apply to redeemer

commit dd0e9f1fd7
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 10:06:27 2025 +0100

    fix: referral utils apply to both coupon

commit e40e5a06f9
Author: John Yeo <johnyeocx@gmail.com>
Date:   Mon Sep 22 09:53:38 2025 +0100

    chore: fixing reward migrations

commit e161cbec83
Merge: e8ff791a 6040c103
Author: John Yeo <johnyeocx@gmail.com>
Date:   Sun Sep 21 18:24:24 2025 +0100

    Merge branch 'staging' into feat/reward-migrations

commit b3952a2702
Author: Muhammad Usman <muhammadusmandev@yahoo.com>
Date:   Sat Sep 20 08:28:35 2025 -0700

    Fix: Create product prevent API call if product name field is empty and not valid

commit 1f609eb592
Author: sidgaikwad <gaikwadsiddharth039@gmail.com>
Date:   Sat Sep 20 00:53:46 2025 +0530

    changes for the update logic for passing proper id

commit 86222adfa9
Author: sidgaikwad <gaikwadsiddharth039@gmail.com>
Date:   Sat Sep 20 00:20:44 2025 +0530

    `feat(rewardPrograms): Update RewardProgramService to support idOrInternalId and add update endpoint`

commit ab655107fc
Author: sidgaikwad <gaikwadsiddharth039@gmail.com>
Date:   Fri Sep 19 23:21:05 2025 +0530

    changes to have view for the updaterewardprogram and also the service for it

commit e8ff791ace
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Fri Sep 19 12:58:48 2025 +0100

    fix: 🐛 dont use unmatched prices

commit e2528194fd
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Fri Sep 19 12:53:06 2025 +0100

    feat: 🎸 toast to alert of any unmigrated prices

commit 7e201dae41
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Tue Sep 16 14:47:04 2025 +0100

    fix: 🐛 only counted row instead of value

commit 590a2ae86f
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Tue Sep 16 14:23:26 2025 +0100

    refactor: 💡 clean up

commit d529d6adb7
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Tue Sep 16 13:52:35 2025 +0100

    refactor: 💡 types

commit d6e9802cdc
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Tue Sep 16 13:52:28 2025 +0100

    feat: 🎸 reward migrations

commit 609bd24b60
Author: shreyash1231 <chandwadkarshreyash@gmail.com>
Date:   Tue Sep 16 17:08:33 2025 +0530

    fix/Disabled-Create-Customer-Button-Until-Id-Or-Email-is-Filled-To-Prevent-API-CALL

commit d13505621c
Author: shreyash1231 <chandwadkarshreyash@gmail.com>
Date:   Tue Sep 16 16:41:45 2025 +0530

    Disabled-Create-Customer-Button-Until-Name-Id-Or-Email-isfilled-to-Prevent-API-CALL

commit c26bd73627
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Mon Sep 15 15:25:47 2025 +0100

    refactor: 💡 type sugar :)

commit 77790cb176
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Mon Sep 15 15:11:05 2025 +0100

    fix: 🐛 applying to all

commit fe96c87a7a
Author: amianthus <49116958+SirTenzin@users.noreply.github.com>
Date:   Mon Sep 15 14:47:16 2025 +0100

    fix: 🐛 timezones not localised
2025-09-22 13:49:19 +00:00

443 lines
8.5 KiB
TypeScript

import {
AggregateType,
AllowanceType,
type AppEnv,
BillingInterval,
BillWhen,
CouponDurationType,
type CreateFreeTrial,
EntInterval,
type Entitlement,
type Feature,
FeatureType,
FeatureUsageType,
FreeTrialDuration,
type Organization,
PriceType,
type ProductItem,
RewardReceivedBy,
RewardTriggerEvent,
RewardType,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { attachPmToCus } from "@/external/stripe/stripeCusUtils.js";
import { generateId } from "@/utils/genUtils.js";
import { getAxiosInstance } from "./setup.js";
export const keyToTitle = (key: string) => {
return key
.replace(/[_-]/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
};
export const initFeature = ({
id,
type,
creditSchema = [],
aggregateType = AggregateType.Sum,
groupBy = "",
eventName,
usageType = FeatureUsageType.Single,
}: {
id: string;
type: FeatureType;
creditSchema?: {
metered_feature_id: string;
feature_amount: number;
credit_amount: number;
}[];
aggregateType?: AggregateType;
groupBy?: string;
eventName?: string;
usageType?: FeatureUsageType;
}): (Feature & { eventName: string }) | any => {
const name = keyToTitle(id);
if (type == FeatureType.Boolean) {
return {
id,
name,
type,
} as Feature;
}
if (type == FeatureType.Metered) {
return {
id,
name,
type,
config: {
filters: [
{
value: eventName ? [eventName] : [id],
property: "",
operator: "",
},
],
aggregate: {
type: aggregateType,
property: "value",
},
usage_type: usageType,
// group_by: groupBy
// ? {
// property: groupBy,
// }
// : undefined,
},
} as Feature;
}
if (type == FeatureType.CreditSystem) {
return {
id,
name,
type,
config: {
schema: creditSchema,
usage_type: FeatureUsageType.Single,
},
} as Feature;
}
throw new Error(`Invalid feature type: ${type}`);
};
export const initEntitlement = ({
feature,
allowance,
interval = EntInterval.Month,
allowanceType = AllowanceType.Fixed,
entityFeatureId,
carryFromPrevious = false,
}: {
feature: Feature;
allowance?: number;
interval?: EntInterval;
allowanceType?: AllowanceType;
entityFeatureId?: string;
carryFromPrevious?: boolean;
}) => {
if (feature.type == FeatureType.Boolean) {
return {
feature_id: feature.id,
internal_feature_id: feature.internal_id,
} as Entitlement;
}
const isUnlimitedOrNone =
allowanceType == AllowanceType.Unlimited || allowance == null;
return {
feature_id: feature.id,
internal_feature_id: feature.internal_id,
allowance_type: allowanceType,
allowance: isUnlimitedOrNone ? null : allowance,
interval: isUnlimitedOrNone ? null : interval,
entity_feature_id: entityFeatureId,
carry_from_previous: carryFromPrevious,
created_at: Date.now(),
id: generateId("ent"),
} as Entitlement;
};
export const initPrice = ({
type,
feature,
billingInterval = BillingInterval.Month,
amount = 10.0,
oneTier = false,
billingUnits = 10,
}: {
type:
| "monthly"
| "in_advance"
| "in_arrears"
| "fixed_cycle"
| "in_arrear_prorated";
feature?: Feature;
billingInterval?: BillingInterval;
amount?: number;
oneTier?: boolean;
billingUnits?: number;
}) => {
if (type == "monthly" || type == "fixed_cycle") {
return {
name: type == "monthly" ? "Monthly" : "Fixed Cycle",
config: {
type: PriceType.Fixed,
amount: amount,
interval: billingInterval,
},
};
}
if (!feature) {
throw new Error("Feature is required for in_advance and in_arrears");
}
if (type == "in_advance") {
return {
name: "In Advance",
config: {
type: PriceType.Usage,
bill_when: BillWhen.StartOfPeriod,
feature_id: feature!.id,
interval: billingInterval,
billing_units: billingUnits,
usage_tiers: [
{
from: 0,
to: -1,
amount: amount || 10.0,
},
],
},
};
}
if (type == "in_arrears" || type == "in_arrear_prorated") {
return {
name: "In Arrears",
config: {
type: PriceType.Usage,
bill_when: BillWhen.EndOfPeriod,
feature_id: feature!.id,
interval: billingInterval,
billing_units: billingUnits,
should_prorate: type == "in_arrear_prorated",
usage_tiers: oneTier
? [
{
from: 0,
to: -1,
amount: amount || 0.01,
},
]
: [
{
from: 0,
to: 10,
amount: 0.5,
},
{
from: 11,
to: -1,
amount: 0.25,
},
],
},
};
}
};
export const initFreeTrial = ({
length,
uniqueFingerprint = false,
cardRequired = true,
}: {
length: number;
uniqueFingerprint?: boolean;
cardRequired?: boolean;
}): CreateFreeTrial => {
return {
length,
unique_fingerprint: uniqueFingerprint,
duration: FreeTrialDuration.Day,
card_required: cardRequired,
};
};
export const initProduct = ({
id,
isDefault = false,
isAddOn = false,
items,
entitlements,
prices,
freeTrial,
group = "",
}: {
id: string;
isDefault?: boolean;
isAddOn?: boolean;
items?: Record<string, ProductItem>;
entitlements: Record<string, Entitlement>;
prices: any[];
freeTrial: CreateFreeTrial | null;
group?: string;
}) => {
// if (notNullish(items)) {
// return {
// id,
// name: keyToTitle(id),
// is_default: isDefault,
// is_add_on: isAddOn,
// group: group,
// items: items,
// free_trial: freeTrial,
// };
// }
return {
id,
name: keyToTitle(id),
is_default: isDefault,
is_add_on: isAddOn,
entitlements: entitlements,
prices,
free_trial: freeTrial,
group: group,
};
};
export const initCustomer = async ({
customer_data,
customerId,
attachPm = false,
db,
org,
env,
testClockId,
}: {
customer_data?: {
id: string;
name?: string;
email?: string;
fingerprint?: string;
};
customerId?: string;
attachPm?: boolean;
db: DrizzleCli;
org: Organization;
env: AppEnv;
testClockId?: string;
}) => {
const axiosInstance = getAxiosInstance();
if (!customerId && !customer_data) {
throw new Error("customerId or customer_data is required");
}
const customerData = customerId
? {
id: customerId,
name: customerId,
email: `${customerId}@example.com`,
}
: customer_data;
// Delete customer if exists
try {
await axiosInstance.delete(`/v1/customers/${customerData!.id}`);
// console.log(" - Successfully deleted customer");
} catch (error) {
// console.log("Failed to delete customer");
}
try {
const { data } = await axiosInstance.post(`/v1/customers`, customerData);
// Attach stripe card
if (attachPm) {
await attachPmToCus({
customer: data.customer,
org: org,
env: env,
db: db,
testClockId: testClockId,
});
}
return data.customer;
} catch (error) {
console.log("Failed to create customer", error);
}
};
// Init Reward
export const initReward = ({
id,
type = RewardType.PercentageDiscount,
discountValue,
durationType = CouponDurationType.OneOff,
durationValue = 0,
onlyUsagePrices = false,
productIds,
applyToAll = false,
freeProductId,
freeProductConfig,
}: {
id: string;
type?: RewardType;
discountValue?: number;
durationType?: CouponDurationType;
durationValue?: number;
onlyUsagePrices?: boolean;
productIds?: string[];
applyToAll?: boolean;
freeProductId?: string;
freeProductConfig?: {
durationType: CouponDurationType;
durationValue: number;
};
}): any => {
if (
type === RewardType.PercentageDiscount ||
type === RewardType.FixedDiscount ||
type === RewardType.InvoiceCredits
) {
return {
id,
name: keyToTitle(id),
type,
only_usage_prices: onlyUsagePrices,
product_ids: productIds,
discount_config: {
discount_value: discountValue,
duration_type: durationType,
duration_value: durationValue,
apply_to_all: applyToAll,
},
};
} else if (type === RewardType.FreeProduct) {
return {
id,
name: keyToTitle(id),
type,
free_product_id: freeProductId,
free_product_config: {
duration_type: freeProductConfig?.durationType,
duration_value: freeProductConfig?.durationValue,
},
};
}
};
export const initRewardProgram = ({
id,
when = RewardTriggerEvent.CustomerCreation,
productIds = [],
internalRewardId,
maxRedemptions = 2,
receivedBy = RewardReceivedBy.Referrer,
}: {
id: string;
productIds?: string[];
internalRewardId: string;
when?: RewardTriggerEvent;
maxRedemptions?: number;
receivedBy?: RewardReceivedBy;
}): any => {
return {
id,
when,
product_ids: productIds,
internal_reward_id: internalRewardId,
max_redemptions: maxRedemptions,
received_by: receivedBy,
};
};