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, event_names: eventName ? [eventName] : [id], config: { 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; entitlements: Record; 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, }; };