Files
cfw-autumn/server/src/internal/orgs/orgUtils.ts
2025-10-21 10:39:35 +01:00

313 lines
7.1 KiB
TypeScript

import {
AppEnv,
ErrCode,
type FrontendOrg,
type Organization,
type OrgConfig,
organizations,
} from "@autumn/shared";
import { eq } from "drizzle-orm";
import Stripe from "stripe";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { CacheManager } from "@/external/caching/CacheManager.js";
import {
orgToAccountId,
shouldUseMaster,
} from "@/external/connect/connectUtils.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { decryptData, generatePublishableKey } from "@/utils/encryptUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { FeatureService } from "../features/FeatureService.js";
import { OrgService } from "./OrgService.js";
import { clearOrgCache } from "./orgUtils/clearOrgCache.js";
import { toSuccessUrl } from "./orgUtils/convertOrgUtils.js";
export const shouldReconnectStripe = async ({
org,
env,
logger,
stripeKey,
}: {
org: Organization;
env: AppEnv;
logger: any;
stripeKey: string;
}) => {
if (!isStripeConnected({ org, env })) return true;
try {
const stripeCli = createStripeCli({ org, env });
const newKey = new Stripe(stripeKey);
const oldAccount = await stripeCli.accounts.retrieve();
const newAccount = await newKey.accounts.retrieve();
return oldAccount.id !== newAccount.id;
} catch (error) {
logger.error("Error checking if stripe should be reconnected", { error });
return true;
}
};
export const isStripeConnected = ({
org,
env,
throughSecretKey = false,
throughAccountId = false,
excludeDefault = false,
}: {
org: Organization;
env?: AppEnv;
throughSecretKey?: boolean;
throughAccountId?: boolean;
excludeDefault?: boolean;
}) => {
const testAccountId = orgToAccountId({
org,
env: AppEnv.Sandbox,
noDefaultAccount: excludeDefault,
});
const liveAccountId = orgToAccountId({
org,
env: AppEnv.Live,
noDefaultAccount: excludeDefault,
});
if (env === AppEnv.Sandbox) {
if (throughAccountId) {
return notNullish(testAccountId);
}
if (throughSecretKey) {
return notNullish(org.stripe_config?.test_api_key);
}
return (
notNullish(org.stripe_config?.test_api_key) || notNullish(testAccountId)
);
} else if (env === AppEnv.Live) {
if (throughAccountId) {
return notNullish(liveAccountId);
}
if (throughSecretKey) {
return notNullish(org.stripe_config?.live_api_key);
}
return (
notNullish(org.stripe_config?.live_api_key) || notNullish(liveAccountId)
);
} else {
return (
notNullish(org.stripe_config?.test_api_key) &&
notNullish(org.stripe_config?.live_api_key)
);
}
};
export const constructOrg = ({ id, slug }: { id: string; slug: string }) => {
return {
id,
slug,
created_at: Date.now(),
default_currency: "usd",
stripe_connected: false,
stripe_config: null,
test_pkey: generatePublishableKey(AppEnv.Sandbox),
live_pkey: generatePublishableKey(AppEnv.Live),
svix_config: {
sandbox_app_id: "",
live_app_id: "",
},
config: {} as any,
};
};
export const deleteStripeWebhook = async ({
org,
env,
}: {
org: Organization;
env: AppEnv;
}) => {
if (!isStripeConnected({ org, env, throughSecretKey: true })) return;
const stripeCli = createStripeCli({ org, env, throughSecretKey: true });
const webhookEndpoints = await stripeCli.webhookEndpoints.list({
limit: 100,
});
for (const webhook of webhookEndpoints.data) {
if (webhook.url.includes(org.id) && webhook.url.includes(env)) {
try {
await stripeCli.webhookEndpoints.del(webhook.id);
console.log(`Deleted stripe webhook (${env}) ${webhook.url}`);
} catch (error: any) {
console.log(`Failed to delete stripe webhook (${env}) ${webhook.url}`);
console.log(error.message);
}
}
}
};
export const getStripeWebhookSecret = (org: Organization, env: AppEnv) => {
const webhookSecret =
env === AppEnv.Sandbox
? org.stripe_config?.test_webhook_secret
: org.stripe_config?.live_webhook_secret;
if (!webhookSecret) {
throw new RecaseError({
code: ErrCode.StripeConfigNotFound,
message: `Stripe webhook secret not found for org ${org.id}`,
statusCode: 400,
});
}
return decryptData(webhookSecret);
};
export const initDefaultConfig = () => {
return {
free_trial_paid_to_paid: false,
// 1. Upgrade prorates immediately
bill_upgrade_immediately: true,
// 2. Convert invoice to charge automatically
convert_to_charge_automatically: false,
};
};
export const createOrgResponse = ({
org,
env,
}: {
org: Organization;
env: AppEnv;
}): FrontendOrg => {
const accountId = orgToAccountId({ org, env, noDefaultAccount: true });
const secretKeyConnected = isStripeConnected({
org,
env,
throughSecretKey: true,
});
const stripeConnection = secretKeyConnected
? "secret_key"
: accountId
? "oauth"
: "default";
const throughMaster = shouldUseMaster({ org, env });
return {
id: org.id,
name: org.name,
logo: org.logo,
slug: org.slug,
master: org.master
? {
id: org.master.id,
name: org.master.name,
slug: org.master.slug,
}
: null,
// sandbox_config: {
// stripe_connected: isStripeConnected({ org, env: AppEnv.Sandbox }),
// default_currency: org.default_currency || "USD",
// return_url: org.sandbox_config?.return_url || "",
// },
// production_config: {
// stripe_connected: isStripeConnected({ org, env: AppEnv.Live }),
// default_currency: org.default_currency || "USD",
// return_url: org.production_config?.return_url || "",
// },
success_url: toSuccessUrl({ org, env }) || "",
default_currency: org.default_currency || "usd",
stripe_connection: stripeConnection,
through_master: throughMaster,
created_at: new Date(org.createdAt).getTime(),
test_pkey: org.test_pkey,
live_pkey: org.live_pkey,
onboarded: org.onboarded ?? true,
deployed: org.deployed ?? true,
};
};
export const getOrgAndFeatures = async ({ req }: { req: any }) => {
const { orgId, env } = req;
const [org, features] = await Promise.all([
OrgService.getFromReq(req),
FeatureService.getFromReq(req),
]);
return { org, features };
};
export const updateOrgConfig = async ({
db,
org,
config,
disconnectCache = true,
}: {
db: DrizzleCli;
org: Organization;
config: Partial<OrgConfig>;
disconnectCache?: boolean;
}) => {
await db
.update(organizations)
.set({
config: {
...org.config,
...config,
},
})
.where(eq(organizations.id, org.id));
await clearOrgCache({
db,
orgId: org.id,
});
if (disconnectCache) {
await CacheManager.disconnect();
}
};
export const unsetOrgStripeKeys = async ({
org,
env,
db,
}: {
org: Organization;
env: AppEnv;
db: DrizzleCli;
}) => {
const newStripeConfig: any = structuredClone(org.stripe_config) || {};
if (env === AppEnv.Sandbox) {
newStripeConfig.test_api_key = null;
newStripeConfig.test_webhook_secret = null;
} else {
newStripeConfig.live_api_key = null;
newStripeConfig.live_webhook_secret = null;
}
await OrgService.update({
db,
orgId: org.id,
updates: {
stripe_config: newStripeConfig,
},
});
};
export const orgToCurrency = ({ org }: { org: Organization }) => {
return org.default_currency || "usd";
};