midway adding stripe connect, refactored a bunch
This commit is contained in:
@@ -94,7 +94,7 @@
|
||||
"recaseai": "^0.0.37",
|
||||
"resend": "^4.1.1",
|
||||
"semver": "^7.7.2",
|
||||
"stripe": "^18.4.0",
|
||||
"stripe": "18.4.0",
|
||||
"svix": "^1.45.1",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"ws": "^8.18.0",
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
|
||||
import type Stripe from "stripe";
|
||||
import { initDrizzle } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createSupabaseClient } from "@/external/supabaseUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UTCDate } from "@date-fns/utc";
|
||||
import chalk from "chalk";
|
||||
import { format, getDate, getMonth, setDate } from "date-fns";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
|
||||
23
server/src/external/connect/connectUtils.ts
vendored
Normal file
23
server/src/external/connect/connectUtils.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
|
||||
export const orgToAccountId = ({
|
||||
org,
|
||||
env,
|
||||
noDefaultAccount = false,
|
||||
}: {
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
noDefaultAccount?: boolean;
|
||||
}): string | undefined => {
|
||||
if (env === AppEnv.Sandbox) {
|
||||
if (noDefaultAccount) {
|
||||
return org.stripe_connect?.test_account_id;
|
||||
}
|
||||
return (
|
||||
org.stripe_connect?.test_account_id ||
|
||||
org.stripe_connect?.default_account_id
|
||||
);
|
||||
} else {
|
||||
return org.stripe_connect?.live_account_id;
|
||||
}
|
||||
};
|
||||
47
server/src/external/connect/createStripeCli.ts
vendored
Normal file
47
server/src/external/connect/createStripeCli.ts
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
AppEnv,
|
||||
ErrCode,
|
||||
type Organization,
|
||||
RecaseError,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { decryptData } from "@/utils/encryptUtils.js";
|
||||
import { orgToAccountId } from "./connectUtils.js";
|
||||
import { initMasterStripe } from "./initMasterStripe.js";
|
||||
|
||||
export const createStripeCli = ({
|
||||
org,
|
||||
env,
|
||||
// apiVersion,
|
||||
legacyVersion,
|
||||
}: {
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
// apiVersion?: string;
|
||||
legacyVersion?: boolean;
|
||||
}) => {
|
||||
// Look at test account flow first
|
||||
const accountId = orgToAccountId({ org, env });
|
||||
|
||||
if (accountId) return initMasterStripe({ accountId, legacyVersion });
|
||||
|
||||
const encrypted =
|
||||
env === AppEnv.Sandbox
|
||||
? org.stripe_config?.test_api_key
|
||||
: org.stripe_config?.live_api_key;
|
||||
|
||||
if (!encrypted) {
|
||||
throw new RecaseError({
|
||||
message: `Please connect your Stripe ${env === AppEnv.Sandbox ? "test" : "live"} secret key. You can find it here: https://dashboard.stripe.com${env === AppEnv.Sandbox ? "/test" : ""}/apikeys`,
|
||||
code: ErrCode.StripeConfigNotFound,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const decrypted = decryptData(encrypted);
|
||||
return new Stripe(decrypted, {
|
||||
apiVersion: legacyVersion
|
||||
? ("2025-02-24.acacia" as any)
|
||||
: "2025-07-30.basil",
|
||||
});
|
||||
};
|
||||
25
server/src/external/connect/initMasterStripe.ts
vendored
Normal file
25
server/src/external/connect/initMasterStripe.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import { InternalError } from "@autumn/shared";
|
||||
import "dotenv/config";
|
||||
import Stripe from "stripe";
|
||||
|
||||
export const initMasterStripe = (params?: {
|
||||
accountId?: string;
|
||||
legacyVersion?: boolean;
|
||||
}) => {
|
||||
if (!process.env.STRIPE_SECRET_KEY) {
|
||||
throw new InternalError({
|
||||
message: "STRIPE_SECRET_KEY env variable is not set",
|
||||
});
|
||||
}
|
||||
|
||||
if (!params) {
|
||||
return new Stripe(process.env.STRIPE_SECRET_KEY || "");
|
||||
}
|
||||
|
||||
return new Stripe(process.env.STRIPE_SECRET_KEY || "", {
|
||||
stripeAccount: params?.accountId,
|
||||
apiVersion: params?.legacyVersion
|
||||
? ("2025-02-24.acacia" as any)
|
||||
: "2025-07-30.basil",
|
||||
});
|
||||
};
|
||||
15
server/src/external/logtail/logtailUtils.ts
vendored
15
server/src/external/logtail/logtailUtils.ts
vendored
@@ -103,20 +103,5 @@ export const createLogger = () => {
|
||||
return createLoggerStructure(pinoLogger);
|
||||
};
|
||||
|
||||
// export const createLogtailAll = () => {
|
||||
// if (
|
||||
// !process.env.LOGTAIL_ALL_SOURCE_TOKEN ||
|
||||
// !process.env.LOGTAIL_ALL_INGESTING_HOST
|
||||
// ) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// const logtail = new Logtail(process.env.LOGTAIL_ALL_SOURCE_TOKEN!, {
|
||||
// endpoint: process.env.LOGTAIL_ALL_INGESTING_HOST!,
|
||||
// });
|
||||
|
||||
// return logtail;
|
||||
// };
|
||||
|
||||
export const logger = createLogger();
|
||||
export type Logger = ReturnType<typeof createLogger>;
|
||||
|
||||
16
server/src/external/posthog/createPosthogCli.ts
vendored
16
server/src/external/posthog/createPosthogCli.ts
vendored
@@ -1,16 +0,0 @@
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { PostHog } from "posthog-node";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
export const createPosthogCli = () => {
|
||||
if (!process.env.POSTHOG_API_KEY) {
|
||||
logger.warn("POSTHOG_API_KEY not set, skipping posthog");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PostHog(process.env.POSTHOG_API_KEY, {
|
||||
host: process.env.POSTHOG_HOST_URL ?? "https://us.i.posthog.com",
|
||||
});
|
||||
};
|
||||
20
server/src/external/posthog/posthogCapture.ts
vendored
20
server/src/external/posthog/posthogCapture.ts
vendored
@@ -1,20 +0,0 @@
|
||||
import { EventMessage, PostHog } from "posthog-node";
|
||||
|
||||
export const posthogCapture = ({
|
||||
posthog,
|
||||
params,
|
||||
}: {
|
||||
posthog?: PostHog;
|
||||
params: EventMessage;
|
||||
}) => {
|
||||
try {
|
||||
if (process.env.NODE_ENV === "development" || !posthog) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture(params);
|
||||
} catch (error) {
|
||||
console.error("Failed to capture posthog event", params);
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
6
server/src/external/redis/redisUtils.ts
vendored
6
server/src/external/redis/redisUtils.ts
vendored
@@ -29,9 +29,9 @@ export const handleAttachRaceCondition = async ({
|
||||
const originalJson = res.json;
|
||||
res.json = async function (body: any) {
|
||||
try {
|
||||
await clearLock({ lockKey, logger: req.logtail });
|
||||
await clearLock({ lockKey, logger: req.logger });
|
||||
} catch (error) {
|
||||
req.logtail.warn("❗️❗️ Error clearing lock", {
|
||||
req.logger.warn("❗️❗️ Error clearing lock", {
|
||||
error,
|
||||
});
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export const handleAttachRaceCondition = async ({
|
||||
throw error;
|
||||
}
|
||||
|
||||
req.logtail.warn("❗️❗️ Error acquiring lock", {
|
||||
req.logger.warn("❗️❗️ Error acquiring lock", {
|
||||
error,
|
||||
});
|
||||
return null;
|
||||
|
||||
8
server/src/external/resend/loopsUtils.ts
vendored
8
server/src/external/resend/loopsUtils.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import type { User } from "better-auth";
|
||||
import { LoopsClient } from "loops";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
import { User } from "better-auth";
|
||||
|
||||
const createLoopsCli = () => {
|
||||
return new LoopsClient(process.env.LOOPS_API_KEY || "");
|
||||
@@ -10,9 +10,9 @@ export const createLoopsContact = async (user: User) => {
|
||||
if (!process.env.LOOPS_API_KEY) return;
|
||||
|
||||
try {
|
||||
let email = user.email;
|
||||
let firstName = user.name?.split(" ")[0] || "";
|
||||
let lastName = user.name?.split(" ")[1] || "";
|
||||
const email = user.email;
|
||||
const firstName = user.name?.split(" ")[0] || "";
|
||||
const lastName = user.name?.split(" ")[1] || "";
|
||||
const loops = createLoopsCli();
|
||||
|
||||
const resp = await loops.createContact(email, {
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
RewardType,
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { pricesOnlyOneOff } from "@/internal/products/prices/priceUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
|
||||
const couponToStripeDuration = ({
|
||||
coupon,
|
||||
|
||||
2
server/src/external/stripe/stripeCusUtils.ts
vendored
2
server/src/external/stripe/stripeCusUtils.ts
vendored
@@ -8,9 +8,9 @@ import {
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { createStripeCli } from "./utils.js";
|
||||
|
||||
export const getStripeCus = async ({
|
||||
stripeCli,
|
||||
|
||||
15
server/src/external/stripe/stripeEnsureUtils.ts
vendored
15
server/src/external/stripe/stripeEnsureUtils.ts
vendored
@@ -1,11 +1,10 @@
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { Stripe } from "stripe";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AppEnv, Organization, products } from "@autumn/shared";
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { initProductInStripe } from "@/internal/products/productUtils.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { createStripeCli } from "./utils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
export async function ensureStripeProducts({
|
||||
db,
|
||||
@@ -55,9 +54,9 @@ export async function ensureStripeProductsWithEnv({
|
||||
const updatedOrg = await OrgService.get({ db, orgId: req.org.id });
|
||||
|
||||
const batchInit: Promise<void>[] = [];
|
||||
for (let fullProduct of fullProducts) {
|
||||
for (const fullProduct of fullProducts) {
|
||||
const initProduct = async () => {
|
||||
let existsInStripe = products.data.find(
|
||||
const existsInStripe = products.data.find(
|
||||
(p) => p.id === fullProduct.processor?.id,
|
||||
);
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
type Product,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { createStripeCli } from "./utils.js";
|
||||
|
||||
export const createStripeProduct = async (
|
||||
org: Organization,
|
||||
|
||||
65
server/src/external/stripe/stripeWebhooks.ts
vendored
65
server/src/external/stripe/stripeWebhooks.ts
vendored
@@ -1,32 +1,29 @@
|
||||
import express, { Router } from "express";
|
||||
import stripe, { Stripe } from "stripe";
|
||||
import { type AppEnv, AuthType, type Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
|
||||
import express, { type Router } from "express";
|
||||
import stripe, { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { AppEnv, AuthType, Organization } from "@autumn/shared";
|
||||
|
||||
import { handleCheckoutSessionCompleted } from "./webhookHandlers/handleCheckoutCompleted.js";
|
||||
import { handleSubscriptionUpdated } from "./webhookHandlers/handleSubUpdated.js";
|
||||
import { handleSubDeleted } from "./webhookHandlers/handleSubDeleted.js";
|
||||
import { handleSubCreated } from "./webhookHandlers/handleSubCreated.js";
|
||||
import {
|
||||
getStripeWebhookSecret,
|
||||
isStripeConnected,
|
||||
unsetOrgStripeKeys,
|
||||
} from "@/internal/orgs/orgUtils.js";
|
||||
import { handleInvoicePaid } from "./webhookHandlers/handleInvoicePaid.js";
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { handleCheckoutSessionCompleted } from "./webhookHandlers/handleCheckoutCompleted.js";
|
||||
import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
|
||||
import { handleInvoiceCreated } from "./webhookHandlers/handleInvoiceCreated/handleInvoiceCreated.js";
|
||||
import { handleInvoiceFinalized } from "./webhookHandlers/handleInvoiceFinalized.js";
|
||||
import { handleSubscriptionScheduleCanceled } from "./webhookHandlers/handleSubScheduleCanceled.js";
|
||||
import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { createStripeCli } from "./utils.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { handleInvoicePaid } from "./webhookHandlers/handleInvoicePaid.js";
|
||||
import { handleInvoiceUpdated } from "./webhookHandlers/handleInvoiceUpdated.js";
|
||||
import { disconnectStripe } from "@/internal/orgs/handlers/handleDeleteStripe.js";
|
||||
import { handleSubCreated } from "./webhookHandlers/handleSubCreated.js";
|
||||
import { handleSubDeleted } from "./webhookHandlers/handleSubDeleted.js";
|
||||
import { handleSubscriptionScheduleCanceled } from "./webhookHandlers/handleSubScheduleCanceled.js";
|
||||
import { handleSubscriptionUpdated } from "./webhookHandlers/handleSubUpdated.js";
|
||||
|
||||
export const stripeWebhookRouter: Router = express.Router();
|
||||
|
||||
@@ -37,7 +34,7 @@ const logStripeWebhook = ({
|
||||
req: ExtendedRequest;
|
||||
event: Stripe.Event;
|
||||
}) => {
|
||||
req.logtail.info(
|
||||
req.logger.info(
|
||||
`${chalk.yellow("STRIPE").padEnd(18)} ${event.type.padEnd(30)} ${req.org.slug} | ${event.id}`,
|
||||
);
|
||||
};
|
||||
@@ -47,7 +44,7 @@ stripeWebhookRouter.post(
|
||||
express.raw({ type: "application/json" }),
|
||||
async (request: any, response: any) => {
|
||||
const sig = request.headers["stripe-signature"];
|
||||
let event;
|
||||
let event: Stripe.Event;
|
||||
|
||||
const { orgId, env } = request.params;
|
||||
const { db } = request;
|
||||
@@ -103,13 +100,13 @@ stripeWebhookRouter.post(
|
||||
|
||||
// event = request.body;
|
||||
|
||||
request.logtail = request.logtail.child({
|
||||
request.logger = request.logger.child({
|
||||
context: {
|
||||
context: {
|
||||
// body: request.body,
|
||||
event_type: event.type,
|
||||
event_id: event.id,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
object_id: `${event.data?.object?.id}` || "N/A",
|
||||
authType: AuthType.Stripe,
|
||||
org_id: orgId,
|
||||
@@ -119,7 +116,7 @@ stripeWebhookRouter.post(
|
||||
},
|
||||
});
|
||||
|
||||
let logger = request.logtail;
|
||||
const logger = request.logger;
|
||||
logStripeWebhook({ req: request, event });
|
||||
|
||||
try {
|
||||
@@ -135,7 +132,7 @@ stripeWebhookRouter.post(
|
||||
});
|
||||
break;
|
||||
|
||||
case "customer.subscription.updated":
|
||||
case "customer.subscription.updated": {
|
||||
const subscription = event.data.object;
|
||||
await handleSubscriptionUpdated({
|
||||
req: request,
|
||||
@@ -147,6 +144,7 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.subscription.deleted":
|
||||
await handleSubDeleted({
|
||||
@@ -157,7 +155,7 @@ stripeWebhookRouter.post(
|
||||
});
|
||||
break;
|
||||
|
||||
case "checkout.session.completed":
|
||||
case "checkout.session.completed": {
|
||||
const checkoutSession = event.data.object;
|
||||
await handleCheckoutSessionCompleted({
|
||||
req: request,
|
||||
@@ -168,9 +166,10 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Triggered when payment through Stripe is successful
|
||||
case "invoice.paid":
|
||||
case "invoice.paid": {
|
||||
const invoice = event.data.object;
|
||||
await handleInvoicePaid({
|
||||
db,
|
||||
@@ -181,6 +180,7 @@ stripeWebhookRouter.post(
|
||||
req: request,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.updated":
|
||||
await handleInvoiceUpdated({
|
||||
@@ -191,7 +191,7 @@ stripeWebhookRouter.post(
|
||||
});
|
||||
break;
|
||||
|
||||
case "invoice.created":
|
||||
case "invoice.created": {
|
||||
const createdInvoice = event.data.object;
|
||||
await handleInvoiceCreated({
|
||||
db,
|
||||
@@ -201,8 +201,9 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.finalized":
|
||||
case "invoice.finalized": {
|
||||
const finalizedInvoice = event.data.object;
|
||||
await handleInvoiceFinalized({
|
||||
db,
|
||||
@@ -212,8 +213,9 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "subscription_schedule.canceled":
|
||||
case "subscription_schedule.canceled": {
|
||||
const canceledSchedule = event.data.object;
|
||||
await handleSubscriptionScheduleCanceled({
|
||||
db,
|
||||
@@ -223,6 +225,7 @@ stripeWebhookRouter.post(
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.discount.deleted":
|
||||
await handleCusDiscountDeleted({
|
||||
@@ -310,7 +313,7 @@ export const handleStripeWebhookRefresh = async ({
|
||||
logger: any;
|
||||
}) => {
|
||||
if (coreEvents.includes(eventType)) {
|
||||
let stripeCusId = data.object.customer;
|
||||
const stripeCusId = data.object.customer;
|
||||
if (!stripeCusId) {
|
||||
logger.warn(
|
||||
`stripe webhook cache refresh, object doesn't contain customer id`,
|
||||
@@ -324,7 +327,7 @@ export const handleStripeWebhookRefresh = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let cus = await CusService.getByStripeId({
|
||||
const cus = await CusService.getByStripeId({
|
||||
db,
|
||||
stripeId: stripeCusId,
|
||||
});
|
||||
|
||||
39
server/src/external/stripe/utils.ts
vendored
39
server/src/external/stripe/utils.ts
vendored
@@ -1,47 +1,10 @@
|
||||
import {
|
||||
AppEnv,
|
||||
BillingInterval,
|
||||
ErrCode,
|
||||
type Feature,
|
||||
Infinite,
|
||||
type Organization,
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { decryptData } from "@/utils/encryptUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
|
||||
export const createStripeCli = ({
|
||||
org,
|
||||
env,
|
||||
// apiVersion,
|
||||
legacyVersion,
|
||||
}: {
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
// apiVersion?: string;
|
||||
legacyVersion?: boolean;
|
||||
}) => {
|
||||
const encrypted =
|
||||
env === AppEnv.Sandbox
|
||||
? org.stripe_config?.test_api_key
|
||||
: org.stripe_config?.live_api_key;
|
||||
|
||||
if (!encrypted) {
|
||||
throw new RecaseError({
|
||||
message: `Please connect your Stripe ${env === AppEnv.Sandbox ? "test" : "live"} secret key. You can find it here: https://dashboard.stripe.com${env === AppEnv.Sandbox ? "/test" : ""}/apikeys`,
|
||||
code: ErrCode.StripeConfigNotFound,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const decrypted = decryptData(encrypted);
|
||||
return new Stripe(decrypted, {
|
||||
apiVersion: legacyVersion
|
||||
? ("2025-02-24.acacia" as any)
|
||||
: "2025-07-30.basil",
|
||||
});
|
||||
};
|
||||
import type Stripe from "stripe";
|
||||
|
||||
export const calculateMetered1Price = ({
|
||||
product,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import type { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createFullCusProduct } from "@/internal/customers/add-product/createFullCusProduct.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
@@ -16,10 +17,8 @@ import { getMetadataFromCheckoutSession } from "@/internal/metadata/metadataUtil
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
import { addTaskToQueue } from "@/queue/queueUtils.js";
|
||||
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getEarliestPeriodEnd } from "../stripeSubUtils/convertSubUtils.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import { getOptionsFromCheckoutSession } from "./handleCheckoutCompleted/getOptionsFromCheckout.js";
|
||||
import { handleCheckoutSub } from "./handleCheckoutCompleted/handleCheckoutSub.js";
|
||||
import { handleRemainingSets } from "./handleCheckoutCompleted/handleRemainingSets.js";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AttachBranch } from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { handleAddProduct } from "@/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.js";
|
||||
import { handleOneOffFunction } from "@/internal/customers/attach/attachFunctions/addProductFlow/handleOneOffFunction.js";
|
||||
import { getDefaultAttachConfig } from "@/internal/customers/attach/attachUtils/getAttachConfig.js";
|
||||
@@ -7,7 +8,6 @@ import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getCusPaymentMethod } from "../../stripeCusUtils.js";
|
||||
import { createStripeCli } from "../../utils.js";
|
||||
|
||||
export const handleSetupCheckout = async ({
|
||||
req,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
|
||||
export async function handleCusDiscountDeleted({
|
||||
db,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { EntityService } from "@/internal/api/entities/EntityService.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
} from "../../stripeInvoiceUtils.js";
|
||||
import { subToPeriodStartEnd } from "../../stripeSubUtils/convertSubUtils.js";
|
||||
import { getStripeSubs } from "../../stripeSubUtils.js";
|
||||
import { createStripeCli } from "../../utils.js";
|
||||
import { handleContUsePrices } from "./handleContUsePrices.js";
|
||||
import { handlePrepaidPrices } from "./handlePrepaidPrices.js";
|
||||
import { handleUsagePrices } from "./handleUsagePrices.js";
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import {
|
||||
AppEnv,
|
||||
type AppEnv,
|
||||
CusProductStatus,
|
||||
FullCustomerPrice,
|
||||
InvoiceStatus,
|
||||
Organization,
|
||||
type FullCustomerPrice,
|
||||
type InvoiceStatus,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import Stripe from "stripe";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
|
||||
import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js";
|
||||
import {
|
||||
getFullStripeInvoice,
|
||||
getStripeExpandedInvoice,
|
||||
invoiceToSubId,
|
||||
updateInvoiceIfExists,
|
||||
} from "../stripeInvoiceUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
|
||||
import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js";
|
||||
|
||||
export const handleInvoiceFinalized = async ({
|
||||
db,
|
||||
@@ -68,11 +68,11 @@ export const handleInvoiceFinalized = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let prices = activeProducts.flatMap((cp) =>
|
||||
const prices = activeProducts.flatMap((cp) =>
|
||||
cp.customer_prices.map((cpr: FullCustomerPrice) => cpr.price),
|
||||
);
|
||||
|
||||
let invoiceItems = await getInvoiceItems({
|
||||
const invoiceItems = await getInvoiceItems({
|
||||
stripeInvoice: invoice,
|
||||
prices: prices,
|
||||
logger,
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { handleInvoiceCheckoutPaid } from "@/internal/customers/attach/attachFunctions/invoiceCheckoutPaid/handleInvoiceCheckoutPaid.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
} from "../stripeInvoiceUtils.js";
|
||||
import { lineItemInCusProduct } from "../stripeSubUtils/stripeSubItemUtils.js";
|
||||
import { getStripeSubs } from "../stripeSubUtils.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import { handleInvoicePaidDiscount } from "./handleInvoicePaidDiscount.js";
|
||||
|
||||
const handleOneOffInvoicePaid = async ({
|
||||
@@ -153,7 +153,7 @@ export const handleInvoicePaid = async ({
|
||||
env: AppEnv;
|
||||
event: Stripe.Event;
|
||||
}) => {
|
||||
const logger = req.logtail;
|
||||
const logger = req.logger;
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const invoice = await getFullStripeInvoice({
|
||||
stripeCli,
|
||||
@@ -238,7 +238,7 @@ export const handleInvoicePaid = async ({
|
||||
cusProducts.map((cp) => `${cp.product.name} - ${cp.product.id}`),
|
||||
);
|
||||
|
||||
if (cusProducts.length == 0) {
|
||||
if (cusProducts.length === 0) {
|
||||
cusProducts = activeCusProducts;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Decimal } from "decimal.js";
|
||||
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
deleteCouponFromSub,
|
||||
} from "../stripeCouponUtils/deleteCouponFromCus.js";
|
||||
import { invoiceToSubId } from "../stripeInvoiceUtils.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
|
||||
export const handleInvoicePaidDiscount = async ({
|
||||
db,
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import {
|
||||
type AppEnv,
|
||||
BillingType,
|
||||
CusProductStatus,
|
||||
FullCusProduct,
|
||||
FullCustomerPrice,
|
||||
Organization,
|
||||
Price,
|
||||
type FullCusProduct,
|
||||
type FullCustomerPrice,
|
||||
type Organization,
|
||||
type Price,
|
||||
} from "@autumn/shared";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import { getStripeExpandedInvoice } from "../stripeInvoiceUtils.js";
|
||||
import { SubService } from "@/internal/subscriptions/SubService.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { getBillingType } from "@/internal/products/prices/priceUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getFullStripeSub } from "../stripeSubUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { InvoiceService } from "@/internal/invoices/InvoiceService.js";
|
||||
import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js";
|
||||
import { getBillingType } from "@/internal/products/prices/priceUtils.js";
|
||||
import { SubService } from "@/internal/subscriptions/SubService.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { getStripeExpandedInvoice } from "../stripeInvoiceUtils.js";
|
||||
import {
|
||||
getEarliestPeriodEnd,
|
||||
getEarliestPeriodStart,
|
||||
} from "../stripeSubUtils/convertSubUtils.js";
|
||||
import { getFullStripeSub } from "../stripeSubUtils.js";
|
||||
|
||||
export const handleSubCreated = async ({
|
||||
db,
|
||||
@@ -56,7 +55,7 @@ export const handleSubCreated = async ({
|
||||
}
|
||||
|
||||
// Update autumn sub
|
||||
let autumnSub = await SubService.getFromScheduleId({
|
||||
const autumnSub = await SubService.getFromScheduleId({
|
||||
db,
|
||||
scheduleId: subscription.schedule as string,
|
||||
});
|
||||
@@ -105,9 +104,9 @@ export const handleSubCreated = async ({
|
||||
cusProds.length,
|
||||
);
|
||||
|
||||
let batchUpdate = [];
|
||||
const batchUpdate = [];
|
||||
for (const cusProd of cusProds) {
|
||||
let subIds = cusProd.subscription_ids
|
||||
const subIds = cusProd.subscription_ids
|
||||
? [...cusProd.subscription_ids]
|
||||
: [];
|
||||
subIds.push(subscription.id);
|
||||
@@ -128,7 +127,7 @@ export const handleSubCreated = async ({
|
||||
stripeInvoiceId: subscription.latest_invoice as string,
|
||||
});
|
||||
|
||||
let invoiceItems = await getInvoiceItems({
|
||||
const invoiceItems = await getInvoiceItems({
|
||||
stripeInvoice: invoice,
|
||||
prices: cusProd.customer_prices.map(
|
||||
(cpr: FullCustomerPrice) => cpr.price,
|
||||
@@ -155,19 +154,19 @@ export const handleSubCreated = async ({
|
||||
}
|
||||
|
||||
// Get cus prods for sub
|
||||
let cusProds = await CusProductService.getByStripeSubId({
|
||||
const cusProds = await CusProductService.getByStripeSubId({
|
||||
db,
|
||||
stripeSubId: subscription.id,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
|
||||
let handleInArrearWithEntity = async (cusProd: FullCusProduct) => {
|
||||
const handleInArrearWithEntity = async (cusProd: FullCusProduct) => {
|
||||
if (!cusProd.internal_entity_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let arrearPrices = cusProd.customer_prices
|
||||
const arrearPrices = cusProd.customer_prices
|
||||
.map((cp) => cp.price)
|
||||
.filter(
|
||||
(p: Price) =>
|
||||
@@ -178,9 +177,9 @@ export const handleSubCreated = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let itemsToDelete = [];
|
||||
const itemsToDelete = [];
|
||||
for (const arrearPrice of arrearPrices) {
|
||||
let subItem = subscription.items.data.find(
|
||||
const subItem = subscription.items.data.find(
|
||||
(i) => i.price.id == arrearPrice.config?.stripe_price_id,
|
||||
);
|
||||
|
||||
@@ -211,7 +210,7 @@ export const handleSubCreated = async ({
|
||||
}
|
||||
};
|
||||
|
||||
let batchUpdate = [];
|
||||
const batchUpdate = [];
|
||||
for (const cusProd of cusProds) {
|
||||
batchUpdate.push(handleInArrearWithEntity(cusProd));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { Organization } from "@autumn/shared";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import { SubService } from "@/internal/subscriptions/SubService.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
|
||||
export const handleSubscriptionScheduleCanceled = async ({
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { SubService } from "@/internal/subscriptions/SubService.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { createStripeCli } from "../utils.js";
|
||||
import { handleSchedulePhaseCompleted } from "./handleSubUpdated/handleSchedulePhaseCompleted.js";
|
||||
import {
|
||||
handleSubCanceled,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
cusProductToProduct,
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { activateFutureProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
@@ -11,7 +12,6 @@ import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { createStripeCli } from "../../utils.js";
|
||||
|
||||
export const handleSchedulePhaseCompleted = async ({
|
||||
req,
|
||||
|
||||
@@ -102,7 +102,7 @@ export const handleSubCanceled = async ({
|
||||
|
||||
const canceledFromPortal = canceled && !isAutumnDowngrade;
|
||||
|
||||
const { db, env, logtail: logger } = req;
|
||||
const { db, env, logger } = req;
|
||||
|
||||
if (!canceledFromPortal || updatedCusProducts.length === 0) return;
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { AttachScenario, type FullCusProduct } from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { isMultiProductSub } from "@/internal/customers/attach/mergeUtils/mergeUtils.js";
|
||||
import { getSubScenarioFromCache } from "@/internal/customers/cusCache/subCacheUtils.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { notNullish, nullish } from "@/utils/genUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AttachScenario, FullCusProduct } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { isMultiProductSub } from "@/internal/customers/attach/mergeUtils/mergeUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getSubScenarioFromCache } from "@/internal/customers/cusCache/subCacheUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
const isSubRenewed = ({
|
||||
previousAttributes,
|
||||
sub,
|
||||
@@ -62,7 +63,7 @@ export const handleSubRenewed = async ({
|
||||
sub: Stripe.Subscription;
|
||||
updatedCusProducts: FullCusProduct[];
|
||||
}) => {
|
||||
const { db, org, env, logtail: logger } = req;
|
||||
const { db, org, env, logger } = req;
|
||||
|
||||
const { renewed } = isSubRenewed({
|
||||
previousAttributes: prevAttributes,
|
||||
@@ -76,7 +77,7 @@ export const handleSubRenewed = async ({
|
||||
if (subScenario === AttachScenario.Renew) return;
|
||||
|
||||
const customer = updatedCusProducts[0].customer;
|
||||
let cusProducts = await CusProductService.list({
|
||||
const cusProducts = await CusProductService.list({
|
||||
db,
|
||||
internalCustomerId: customer!.internal_id,
|
||||
});
|
||||
@@ -93,13 +94,13 @@ export const handleSubRenewed = async ({
|
||||
|
||||
if (!org.config.sync_status) return;
|
||||
|
||||
let { curScheduledProduct } = getExistingCusProducts({
|
||||
const { curScheduledProduct } = getExistingCusProducts({
|
||||
product: updatedCusProducts[0].product,
|
||||
cusProducts,
|
||||
internalEntityId: updatedCusProducts[0].internal_entity_id,
|
||||
});
|
||||
|
||||
let deletedCusProducts: FullCusProduct[] = [];
|
||||
const deletedCusProducts: FullCusProduct[] = [];
|
||||
|
||||
if (curScheduledProduct) {
|
||||
logger.info(
|
||||
@@ -115,7 +116,7 @@ export const handleSubRenewed = async ({
|
||||
}
|
||||
|
||||
try {
|
||||
for (let cusProd of updatedCusProducts) {
|
||||
for (const cusProd of updatedCusProducts) {
|
||||
await addProductsUpdatedWebhookTask({
|
||||
req,
|
||||
internalCustomerId: cusProd.internal_customer_id,
|
||||
|
||||
5
server/src/external/svix/svixUtils.ts
vendored
5
server/src/external/svix/svixUtils.ts
vendored
@@ -1,5 +1,4 @@
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { Organization } from "@autumn/shared";
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
import { Svix } from "svix";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
@@ -35,7 +34,7 @@ export const getSvixAppId = ({
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
const svixConfig = org.svix_config;
|
||||
return env == AppEnv.Live
|
||||
return env === AppEnv.Live
|
||||
? svixConfig?.live_app_id
|
||||
: svixConfig?.sandbox_app_id;
|
||||
};
|
||||
|
||||
315
server/src/external/webhooks/connectWebhookRouter.ts
vendored
Normal file
315
server/src/external/webhooks/connectWebhookRouter.ts
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
import { type AppEnv, AuthType, type Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import express, { type Router } from "express";
|
||||
import type { Context } from "hono";
|
||||
import { Stripe } from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { initMasterStripe } from "@/external/connect/initMasterStripe.js";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { unsetOrgStripeKeys } from "@/internal/orgs/orgUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { Logger } from "../logtail/logtailUtils.js";
|
||||
import { handleCheckoutSessionCompleted } from "../stripe/webhookHandlers/handleCheckoutCompleted.js";
|
||||
import { handleCusDiscountDeleted } from "../stripe/webhookHandlers/handleCusDiscountDeleted.js";
|
||||
import { handleInvoiceCreated } from "../stripe/webhookHandlers/handleInvoiceCreated/handleInvoiceCreated.js";
|
||||
import { handleInvoiceFinalized } from "../stripe/webhookHandlers/handleInvoiceFinalized.js";
|
||||
import { handleInvoicePaid } from "../stripe/webhookHandlers/handleInvoicePaid.js";
|
||||
import { handleInvoiceUpdated } from "../stripe/webhookHandlers/handleInvoiceUpdated.js";
|
||||
import { handleSubCreated } from "../stripe/webhookHandlers/handleSubCreated.js";
|
||||
import { handleSubDeleted } from "../stripe/webhookHandlers/handleSubDeleted.js";
|
||||
import { handleSubscriptionScheduleCanceled } from "../stripe/webhookHandlers/handleSubScheduleCanceled.js";
|
||||
import { handleSubscriptionUpdated } from "../stripe/webhookHandlers/handleSubUpdated.js";
|
||||
|
||||
export const connectWebhookRouter: Router = express.Router();
|
||||
|
||||
const logStripeWebhook = ({
|
||||
logger,
|
||||
org,
|
||||
event,
|
||||
}: {
|
||||
logger: Logger;
|
||||
org: Organization;
|
||||
event: Stripe.Event;
|
||||
}) => {
|
||||
logger.info(
|
||||
`${chalk.yellow("STRIPE").padEnd(18)} ${event.type.padEnd(30)} ${org.slug} | ${event.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const handleConnectWebhook = async (c: Context<HonoEnv>) => {
|
||||
const ctx = c.get("ctx");
|
||||
|
||||
const { db, logger } = ctx;
|
||||
|
||||
const masterStripe = initMasterStripe();
|
||||
let event: Stripe.Event;
|
||||
try {
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || "";
|
||||
const rawBody = await c.req.text();
|
||||
const signature = c.req.header("stripe-signature") || "";
|
||||
|
||||
event = await masterStripe.webhooks.constructEventAsync(
|
||||
rawBody,
|
||||
signature,
|
||||
webhookSecret,
|
||||
);
|
||||
} catch (err: any) {
|
||||
logger.error(`Webhook verification error: ${err.message}`, { error: err });
|
||||
return c.json({ error: err.message }, 400);
|
||||
}
|
||||
|
||||
const accountId = event.account;
|
||||
|
||||
if (!accountId) {
|
||||
return c.json({ error: "Account ID not found" }, 400);
|
||||
}
|
||||
|
||||
const { org, features, env } = await OrgService.getByAccountId({
|
||||
db,
|
||||
accountId,
|
||||
});
|
||||
|
||||
ctx.org = org;
|
||||
ctx.features = features;
|
||||
ctx.env = env;
|
||||
ctx.logger = ctx.logger.child({
|
||||
context: {
|
||||
context: {
|
||||
event_type: event.type,
|
||||
event_id: event.id,
|
||||
// @ts-expect-error
|
||||
object_id: `${event.data?.object?.id}` || "N/A",
|
||||
authType: AuthType.Stripe,
|
||||
org_id: org.id,
|
||||
org_slug: org.slug,
|
||||
env,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
logStripeWebhook({ logger, org, event });
|
||||
|
||||
try {
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
switch (event.type) {
|
||||
case "customer.subscription.created":
|
||||
await handleSubCreated({
|
||||
db,
|
||||
org,
|
||||
subData: event.data.object,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
|
||||
case "customer.subscription.updated": {
|
||||
const subscription = event.data.object;
|
||||
await handleSubscriptionUpdated({
|
||||
req: ctx as ExtendedRequest,
|
||||
db,
|
||||
org,
|
||||
subscription,
|
||||
previousAttributes: event.data.previous_attributes,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.subscription.deleted":
|
||||
await handleSubDeleted({
|
||||
req: ctx as ExtendedRequest,
|
||||
stripeCli,
|
||||
data: event.data.object,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
|
||||
case "checkout.session.completed": {
|
||||
const checkoutSession = event.data.object;
|
||||
await handleCheckoutSessionCompleted({
|
||||
req: ctx as ExtendedRequest,
|
||||
db,
|
||||
data: checkoutSession,
|
||||
org,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Triggered when payment through Stripe is successful
|
||||
case "invoice.paid": {
|
||||
const invoice = event.data.object;
|
||||
await handleInvoicePaid({
|
||||
db,
|
||||
org,
|
||||
invoiceData: invoice,
|
||||
env,
|
||||
event,
|
||||
req: ctx as ExtendedRequest,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.updated":
|
||||
await handleInvoiceUpdated({
|
||||
stripeCli,
|
||||
env,
|
||||
event,
|
||||
req: ctx as ExtendedRequest,
|
||||
});
|
||||
break;
|
||||
|
||||
case "invoice.created": {
|
||||
const createdInvoice = event.data.object;
|
||||
await handleInvoiceCreated({
|
||||
db,
|
||||
org,
|
||||
data: createdInvoice,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.finalized": {
|
||||
const finalizedInvoice = event.data.object;
|
||||
await handleInvoiceFinalized({
|
||||
db,
|
||||
org,
|
||||
data: finalizedInvoice,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "subscription_schedule.canceled": {
|
||||
const canceledSchedule = event.data.object;
|
||||
await handleSubscriptionScheduleCanceled({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
schedule: canceledSchedule,
|
||||
logger,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.discount.deleted":
|
||||
await handleCusDiscountDeleted({
|
||||
db,
|
||||
org,
|
||||
discount: event.data.object,
|
||||
env,
|
||||
logger,
|
||||
res: ctx as ExtendedRequest,
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Stripe.errors.StripeError) {
|
||||
if (error.message.includes("No such customer")) {
|
||||
logger.warn(`stripe customer missing: ${error.message}`);
|
||||
return c.json({ message: "ok" }, 200);
|
||||
}
|
||||
|
||||
if (error.message.includes("Expired API Key provided")) {
|
||||
// Disconnect Stripe
|
||||
await unsetOrgStripeKeys({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
return c.json({ message: "ok" }, 200);
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(`Stripe webhook, error: ${error}`, { error });
|
||||
return c.json({ message: "Internal server error" }, 500);
|
||||
}
|
||||
|
||||
try {
|
||||
await handleStripeWebhookRefresh({
|
||||
eventType: event.type,
|
||||
data: event.data,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Stripe webhook, error refreshing cache!`, { error });
|
||||
}
|
||||
|
||||
return c.json({ message: "Webhook received" }, 200);
|
||||
};
|
||||
|
||||
const coreEvents = [
|
||||
"customer.subscription.created",
|
||||
"customer.subscription.updated",
|
||||
"customer.subscription.deleted",
|
||||
"invoice.paid",
|
||||
"invoice.created",
|
||||
"invoice.finalized",
|
||||
"subscription_schedule.canceled",
|
||||
"checkout.session.completed",
|
||||
];
|
||||
|
||||
export const handleStripeWebhookRefresh = async ({
|
||||
eventType,
|
||||
data,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
logger,
|
||||
}: {
|
||||
eventType: string;
|
||||
data: any;
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
logger: any;
|
||||
}) => {
|
||||
if (coreEvents.includes(eventType)) {
|
||||
const stripeCusId = data.object.customer;
|
||||
if (!stripeCusId) {
|
||||
logger.warn(
|
||||
`stripe webhook cache refresh, object doesn't contain customer id`,
|
||||
{
|
||||
data: {
|
||||
eventType,
|
||||
object: data.object,
|
||||
},
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const cus = await CusService.getByStripeId({
|
||||
db,
|
||||
stripeId: stripeCusId,
|
||||
});
|
||||
|
||||
if (!cus) {
|
||||
logger.warn(
|
||||
`Searched for customer by stripe id, but not found: ${stripeCusId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// logger.info(`Deleting cache for customer ${cus.id}`);
|
||||
await deleteCusCache({
|
||||
db,
|
||||
customerId: cus.id!,
|
||||
org,
|
||||
env,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import express, { Router } from "express";
|
||||
|
||||
import { stripeWebhookRouter } from "../stripe/stripeWebhooks.js";
|
||||
import express, { type Router } from "express";
|
||||
import { autumnWebhookRouter } from "../autumn/autumnWebhookRouter.js";
|
||||
import { stripeWebhookRouter } from "../stripe/stripeWebhooks.js";
|
||||
|
||||
const webhooksRouter: Router = express.Router();
|
||||
|
||||
@@ -9,4 +8,6 @@ webhooksRouter.use("/stripe", stripeWebhookRouter);
|
||||
|
||||
webhooksRouter.use("/autumn", autumnWebhookRouter);
|
||||
|
||||
// webhooksRouter.use("/connect", connectWebhookRouter);
|
||||
|
||||
export default webhooksRouter;
|
||||
|
||||
@@ -29,7 +29,6 @@ import { client, db } from "./db/initDrizzle.js";
|
||||
import { CacheManager } from "./external/caching/CacheManager.js";
|
||||
import { ClickHouseManager } from "./external/clickhouse/ClickHouseManager.js";
|
||||
import { logger } from "./external/logtail/logtailUtils.js";
|
||||
import { createPosthogCli } from "./external/posthog/createPosthogCli.js";
|
||||
import webhooksRouter from "./external/webhooks/webhooksRouter.js";
|
||||
import { redirectToHono } from "./initHono.js";
|
||||
import { apiRouter } from "./internal/api/apiRouter.js";
|
||||
@@ -102,8 +101,6 @@ const init = async () => {
|
||||
|
||||
app.all("/api/auth/*", toNodeHandler(auth));
|
||||
|
||||
const posthog = createPosthogCli();
|
||||
|
||||
// Initialize managers in parallel for faster startup
|
||||
await Promise.all([
|
||||
QueueManager.getInstance(),
|
||||
@@ -115,7 +112,6 @@ const init = async () => {
|
||||
req.env = req.env = req.headers.app_env || AppEnv.Sandbox;
|
||||
req.db = db;
|
||||
req.clickhouseClient = await ClickHouseManager.getClient();
|
||||
req.posthog = posthog;
|
||||
req.id = req.headers["rndr-id"] || generateId("local_req");
|
||||
req.timestamp = Date.now();
|
||||
|
||||
@@ -139,12 +135,11 @@ const init = async () => {
|
||||
// Store span on request for potential use in other middleware/handlers
|
||||
req.span = span;
|
||||
|
||||
req.logtail = logger.child({
|
||||
req.logger = logger.child({
|
||||
context: {
|
||||
req: reqContext,
|
||||
},
|
||||
});
|
||||
req.logger = req.logtail;
|
||||
|
||||
const endSpan = () => {
|
||||
try {
|
||||
@@ -177,7 +172,7 @@ const init = async () => {
|
||||
|
||||
app.use(express.json());
|
||||
app.use(async (req: any, res: any, next: any) => {
|
||||
req.logtail.info(`${req.method} ${req.originalUrl}`, {
|
||||
req.logger.info(`${req.method} ${req.originalUrl}`, {
|
||||
context: {
|
||||
body: req.body,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getRequestListener } from "@hono/node-server";
|
||||
import { Hono } from "hono";
|
||||
import { cors } from "hono/cors";
|
||||
import { handleConnectWebhook } from "./external/webhooks/connectWebhookRouter.js";
|
||||
import { analyticsMiddleware } from "./honoMiddlewares/analyticsMiddleware.js";
|
||||
import { apiVersionMiddleware } from "./honoMiddlewares/apiVersionMiddleware.js";
|
||||
import { baseMiddleware } from "./honoMiddlewares/baseMiddleware.js";
|
||||
@@ -12,6 +13,8 @@ import { secretKeyMiddleware } from "./honoMiddlewares/secretKeyMiddleware.js";
|
||||
import { traceMiddleware } from "./honoMiddlewares/traceMiddleware.js";
|
||||
import type { HonoEnv } from "./honoUtils/HonoEnv.js";
|
||||
import { cusRouter } from "./internal/customers/cusRouter.js";
|
||||
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleGetOAuthUrl.js";
|
||||
import { honoOrgRouter } from "./internal/orgs/orgRouter.js";
|
||||
import { honoPlatformRouter } from "./internal/platform/honoPlatformRouter.js";
|
||||
import { honoProductRouter } from "./internal/products/productRouter.js";
|
||||
import { auth } from "./utils/auth.js";
|
||||
@@ -65,33 +68,27 @@ export const createHonoApp = () => {
|
||||
return auth.handler(c.req.raw);
|
||||
});
|
||||
|
||||
// OAuth callback (needs to be before middleware)
|
||||
app.get("/stripe/oauth_callback", handleOAuthCallback);
|
||||
|
||||
// Step 1: Base middleware - sets up ctx (db, logger, etc.)
|
||||
app.use("*", baseMiddleware);
|
||||
|
||||
// Step 2: Tracing middleware - handles OpenTelemetry spans
|
||||
app.use("*", traceMiddleware);
|
||||
|
||||
// Step 4: Auth middleware - verifies secret key and populates auth context
|
||||
// Webhook routes (after baseMiddleware for logging, but baseMiddleware skips body parsing)
|
||||
app.post("/webhooks/connect", handleConnectWebhook);
|
||||
|
||||
app.use("/v1/*", secretKeyMiddleware);
|
||||
|
||||
// Step 5: Org config middleware - allows config overrides via header
|
||||
app.use("/v1/*", orgConfigMiddleware);
|
||||
|
||||
// Step 3: API Version middleware - validates x-api-version header
|
||||
app.use("/v1/*", apiVersionMiddleware);
|
||||
|
||||
// Step 6: Refresh cache middleware - clears customer cache after successful mutations
|
||||
app.use("/v1/*", refreshCacheMiddleware);
|
||||
|
||||
// Step 7: Analytics middleware - enriches logger context and logs responses
|
||||
app.use("/v1/*", analyticsMiddleware);
|
||||
|
||||
// Step 8: Query middleware - handles query parsing and validation
|
||||
app.use("/v1/*", queryMiddleware());
|
||||
|
||||
app.route("v1/customers", cusRouter);
|
||||
app.route("v1/products", honoProductRouter);
|
||||
app.route("v1/platform", honoPlatformRouter);
|
||||
app.route("v1/organization", honoOrgRouter);
|
||||
|
||||
// Error handler - must be defined after all routes and middleware
|
||||
app.onError(errorMiddleware);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ADMIN_USER_IDs } from "@/utils/constants.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
export const withAdminAuth = async (req: any, res: any, next: NextFunction) => {
|
||||
const { logtail: logger, userId } = req as ExtendedRequest;
|
||||
const { logger } = req as ExtendedRequest;
|
||||
|
||||
try {
|
||||
const data = await auth.api.getSession({
|
||||
|
||||
@@ -13,8 +13,6 @@ import { handleCreateBillingPortal } from "../customers/handlers/handleCreateBil
|
||||
import { featureRouter } from "../features/featureRouter.js";
|
||||
import { internalFeatureRouter } from "../features/internalFeatureRouter.js";
|
||||
import { migrationRouter } from "../migrations/migrationRouter.js";
|
||||
import { handleConnectStripe } from "../orgs/handlers/handleConnectStripe.js";
|
||||
import { handleDeleteStripe } from "../orgs/handlers/handleDeleteStripe.js";
|
||||
import { handleGetOrg } from "../orgs/handlers/handleGetOrg.js";
|
||||
import { platformRouter } from "../platform/platformRouter.js";
|
||||
import { productBetaRouter, productRouter } from "../products/productRouter.js";
|
||||
@@ -68,9 +66,9 @@ apiRouter.post("/billing_portal", handleCreateBillingPortal);
|
||||
apiRouter.use("/query", analyticsRouter);
|
||||
apiRouter.use("/platform", platformRouter);
|
||||
|
||||
// Used for tests...
|
||||
apiRouter.post("/organization/stripe", handleConnectStripe);
|
||||
apiRouter.delete("/organization/stripe", handleDeleteStripe);
|
||||
// // Used for tests...
|
||||
// apiRouter.post("/organization/stripe", ...handleConnectStripe);
|
||||
// apiRouter.delete("/organization/stripe", ...handleDeleteStripe);
|
||||
apiRouter.get("/organization", handleGetOrg);
|
||||
|
||||
export { apiRouter };
|
||||
|
||||
@@ -72,7 +72,7 @@ export const handleBatchCustomers = async (req: any, res: any) =>
|
||||
offset: query.offset,
|
||||
features: req.features,
|
||||
statuses: query.statuses ?? [],
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
apiVersion: req.apiVersion,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import {
|
||||
type AppEnv,
|
||||
BillingType,
|
||||
type Customer,
|
||||
type Entitlement,
|
||||
type Entity,
|
||||
EntityExpand,
|
||||
ErrCode,
|
||||
type Feature,
|
||||
type FullCustomerEntitlement,
|
||||
type FullCustomerPrice,
|
||||
type Organization,
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { submitUsageToStripe } from "@/external/stripe/stripeMeterUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import {
|
||||
getBillingType,
|
||||
@@ -8,21 +23,6 @@ import {
|
||||
} from "@/internal/products/prices/priceUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import {
|
||||
AppEnv,
|
||||
BillingType,
|
||||
Customer,
|
||||
Entitlement,
|
||||
Entity,
|
||||
EntityExpand,
|
||||
ErrCode,
|
||||
Feature,
|
||||
FullCustomerEntitlement,
|
||||
FullCustomerPrice,
|
||||
Organization,
|
||||
UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
export const getLinkedCusEnt = ({
|
||||
linkedFeature,
|
||||
@@ -32,7 +32,7 @@ export const getLinkedCusEnt = ({
|
||||
cusEnts: any;
|
||||
}) => {
|
||||
// Get linked cus ent...
|
||||
let linkedCusEnt = cusEnts.find(
|
||||
const linkedCusEnt = cusEnts.find(
|
||||
(e: any) => e.entitlement.feature.id === linkedFeature.id,
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export const entityFeatureIdExists = ({
|
||||
}: {
|
||||
cusEnt: FullCustomerEntitlement;
|
||||
}) => {
|
||||
let ent = cusEnt.entitlement;
|
||||
const ent = cusEnt.entitlement;
|
||||
return notNullish(ent.entity_feature_id);
|
||||
};
|
||||
|
||||
@@ -102,7 +102,7 @@ export const removeEntityFromCusEnt = async ({
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
// isLinked
|
||||
let isLinked = isLinkedToEntity({
|
||||
const isLinked = isLinkedToEntity({
|
||||
cusEnt,
|
||||
entity,
|
||||
});
|
||||
@@ -111,22 +111,22 @@ export const removeEntityFromCusEnt = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let entitlement = cusEnt.entitlement;
|
||||
const entitlement = cusEnt.entitlement;
|
||||
console.log(
|
||||
`Linked cus ent: ${entitlement.feature.id}, isLinked: ${isLinked}`,
|
||||
);
|
||||
|
||||
// Delete cus ent ids
|
||||
let newEntities = structuredClone(cusEnt.entities!);
|
||||
const newEntities = structuredClone(cusEnt.entities!);
|
||||
|
||||
// TODO: Send usage to stripe if cus price exists
|
||||
let stripeCli = createStripeCli({
|
||||
const stripeCli = createStripeCli({
|
||||
org,
|
||||
env,
|
||||
});
|
||||
if (cusPrice) {
|
||||
let config = cusPrice.price.config as UsagePriceConfig;
|
||||
let billingType = getBillingType(config);
|
||||
const config = cusPrice.price.config as UsagePriceConfig;
|
||||
const billingType = getBillingType(config);
|
||||
if (billingType == BillingType.UsageInArrear) {
|
||||
let usage = -newEntities[entity.id]?.balance;
|
||||
|
||||
@@ -163,8 +163,8 @@ export const removeEntityFromCusEnt = async ({
|
||||
|
||||
export const parseEntityExpand = (expand: string): EntityExpand[] => {
|
||||
if (expand) {
|
||||
let options = expand.split(",");
|
||||
let result: EntityExpand[] = [];
|
||||
const options = expand.split(",");
|
||||
const result: EntityExpand[] = [];
|
||||
for (const option of options) {
|
||||
if (!Object.values(EntityExpand).includes(option as EntityExpand)) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { EntityService } from "../EntityService.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { CusProductStatus, ErrCode } from "@autumn/shared";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { handleCustomerRaceCondition } from "@/external/redis/redisUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import {
|
||||
findLinkedCusEnts,
|
||||
findMainCusEntForFeature,
|
||||
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js";
|
||||
import { RepService } from "@/internal/customers/cusProducts/cusEnts/RepService.js";
|
||||
import {
|
||||
deleteEntityFromCusEnt,
|
||||
replaceEntityInCusEnt,
|
||||
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/linkedCusEntUtils.js";
|
||||
import { RepService } from "@/internal/customers/cusProducts/cusEnts/RepService.js";
|
||||
import { cancelSubsForEntity } from "@/internal/entities/handlers/handleDeleteEntity/cancelSubsForEntity.js";
|
||||
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { EntityService } from "../EntityService.js";
|
||||
|
||||
export const handleDeleteEntity = async (req: any, res: any) => {
|
||||
try {
|
||||
const { org, env, db, logtail: logger, features } = req;
|
||||
const { org, env, db, logger, features } = req;
|
||||
const { customer_id, entity_id } = req.params;
|
||||
|
||||
await handleCustomerRaceCondition({
|
||||
@@ -73,9 +73,9 @@ export const handleDeleteEntity = async (req: any, res: any) => {
|
||||
const feature = features.find((f: any) => f.id === entity?.feature_id);
|
||||
|
||||
for (const cusProduct of cusProducts) {
|
||||
let cusEnts = cusProduct.customer_entitlements;
|
||||
const cusEnts = cusProduct.customer_entitlements;
|
||||
|
||||
let mainCusEnt = findMainCusEntForFeature({
|
||||
const mainCusEnt = findMainCusEntForFeature({
|
||||
cusEnts,
|
||||
feature,
|
||||
});
|
||||
@@ -97,12 +97,12 @@ export const handleDeleteEntity = async (req: any, res: any) => {
|
||||
logger,
|
||||
});
|
||||
|
||||
let linkedCusEnts = findLinkedCusEnts({
|
||||
const linkedCusEnts = findLinkedCusEnts({
|
||||
cusEnts: cusProduct.customer_entitlements,
|
||||
feature: mainCusEnt.entitlement.feature,
|
||||
});
|
||||
|
||||
let replaceable =
|
||||
const replaceable =
|
||||
newReplaceables && newReplaceables.length > 0
|
||||
? newReplaceables[0]
|
||||
: null;
|
||||
@@ -121,14 +121,14 @@ export const handleDeleteEntity = async (req: any, res: any) => {
|
||||
for (const linkedCusEnt of linkedCusEnts) {
|
||||
let newEntities;
|
||||
if (replaceable) {
|
||||
let { newEntities: newEntities_ } = replaceEntityInCusEnt({
|
||||
const { newEntities: newEntities_ } = replaceEntityInCusEnt({
|
||||
cusEnt: linkedCusEnt,
|
||||
entityId: entity.id,
|
||||
replaceable,
|
||||
});
|
||||
newEntities = newEntities_;
|
||||
} else {
|
||||
let { newEntities: newEntities_ } = deleteEntityFromCusEnt({
|
||||
const { newEntities: newEntities_ } = deleteEntityFromCusEnt({
|
||||
cusEnt: linkedCusEnt,
|
||||
entityId: entity.id,
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ checkRouter.post("", async (req: any, res: any) => {
|
||||
entity_id,
|
||||
} = req.body;
|
||||
|
||||
const { logtail: logger, db } = req;
|
||||
const { logger, db } = req;
|
||||
|
||||
if (!customer_id) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { CusProductStatus, FullCusProduct, SuccessCode } from "@autumn/shared";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import {
|
||||
CusProductStatus,
|
||||
type FullCusProduct,
|
||||
SuccessCode,
|
||||
} from "@autumn/shared";
|
||||
import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCustomer.js";
|
||||
import { getOrgAndFeatures } from "@/internal/orgs/orgUtils.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
|
||||
import { getProductCheckPreview } from "./getProductCheckPreview.js";
|
||||
|
||||
@@ -21,12 +24,10 @@ export const handleProductCheck = async ({
|
||||
with_preview,
|
||||
entity_data,
|
||||
} = req.body;
|
||||
const { orgId, env, logtail: logger, db } = req;
|
||||
|
||||
let { org, features } = await getOrgAndFeatures({ req });
|
||||
const { orgId, env, logger, db } = req;
|
||||
|
||||
// 1. Get customer and org
|
||||
let [customer, product] = await Promise.all([
|
||||
const [customer, product] = await Promise.all([
|
||||
getOrCreateCustomer({
|
||||
req,
|
||||
customerId: customer_id,
|
||||
@@ -53,15 +54,15 @@ export const handleProductCheck = async ({
|
||||
if (customer.entity) {
|
||||
cusProducts = cusProducts.filter(
|
||||
(cusProduct: FullCusProduct) =>
|
||||
cusProduct.internal_entity_id == customer.entity!.internal_id,
|
||||
cusProduct.internal_entity_id === customer.entity!.internal_id,
|
||||
);
|
||||
}
|
||||
|
||||
let cusProduct: FullCusProduct | undefined = cusProducts.find(
|
||||
const cusProduct: FullCusProduct | undefined = cusProducts.find(
|
||||
(cusProduct: FullCusProduct) => cusProduct.product.id === product_id,
|
||||
);
|
||||
|
||||
let preview = with_preview
|
||||
const preview = with_preview
|
||||
? await getProductCheckPreview({
|
||||
req,
|
||||
customer,
|
||||
@@ -96,7 +97,7 @@ export const handleProductCheck = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
let onTrial =
|
||||
const onTrial =
|
||||
notNullish(cusProduct.trial_ends_at) &&
|
||||
cusProduct.trial_ends_at! > Date.now();
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ export const handleEventSent = async ({
|
||||
customer_id,
|
||||
customer_data,
|
||||
event_data,
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
entityId: event_data.entity_id,
|
||||
entityData: event_data.entity_data,
|
||||
features,
|
||||
|
||||
@@ -141,7 +141,7 @@ export const handleUsageEvent = async ({
|
||||
entity_id,
|
||||
idempotency_key,
|
||||
} = req.body;
|
||||
const { logtail: logger } = req;
|
||||
const { logger } = req;
|
||||
|
||||
if (!customer_id || !feature_id) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
|
||||
import { Router } from "express";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { Router } from "express";
|
||||
|
||||
export const invoiceRouter: Router = Router();
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { RewardProgramService } from "@/internal/rewards/RewardProgramService.js";
|
||||
import { RewardRedemptionService } from "@/internal/rewards/RewardRedemptionService.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
import { triggerRedemption } from "@/internal/rewards/referralUtils.js";
|
||||
import { triggerFreeProduct } from "@/internal/rewards/referralUtils/triggerFreeProduct.js";
|
||||
import { triggerRedemption } from "@/internal/rewards/referralUtils.js";
|
||||
import { getRewardCat } from "@/internal/rewards/rewardUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { generateId, notNullish } from "@/utils/genUtils.js";
|
||||
@@ -24,7 +24,7 @@ export default async (req: any, res: any) =>
|
||||
res,
|
||||
action: "redeem referral code",
|
||||
handler: async (req, res) => {
|
||||
const { orgId, env, logtail: logger, db } = req;
|
||||
const { orgId, env, logger, db } = req;
|
||||
const { code, customer_id: customerId } = req.body;
|
||||
|
||||
// 1. Get redeemed by customer, and referral code
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async (req: any, res: any) =>
|
||||
res,
|
||||
action: "create coupon",
|
||||
handler: async (req, res) => {
|
||||
const { db, orgId, env, logtail: logger } = req;
|
||||
const { db, orgId, env, logger } = req;
|
||||
const rewardBody = req.body;
|
||||
const rewardData = CreateRewardSchema.parse(rewardBody);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ErrCode, PriceType, RewardCategory } from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripeCoupon } from "@/external/stripe/stripeCouponUtils/stripeCouponUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { PriceService } from "@/internal/products/prices/PriceService.js";
|
||||
@@ -16,7 +16,7 @@ export default async (req: any, res: any) =>
|
||||
action: "update coupon",
|
||||
handler: async (req, res) => {
|
||||
const { internalId } = req.params;
|
||||
const { orgId, env, db, logtail: logger } = req;
|
||||
const { orgId, env, db, logger } = req;
|
||||
const rewardBody = req.body;
|
||||
|
||||
const org = await OrgService.getFromReq(req);
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
SuccessCode,
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createCheckoutMetadata } from "@/internal/metadata/metadataUtils.js";
|
||||
import { toSuccessUrl } from "@/internal/orgs/orgUtils/convertOrgUtils.js";
|
||||
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
@@ -31,7 +31,7 @@ export const handleCreateCheckout = async ({
|
||||
config: AttachConfig;
|
||||
returnCheckout?: boolean;
|
||||
}) => {
|
||||
const { db, logtail: logger } = req;
|
||||
const { db, logger } = req;
|
||||
|
||||
const { customer, org, freeTrial, successUrl, rewards } = attachParams;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const handleRenewProduct = async ({
|
||||
attachParams: AttachParams;
|
||||
config: AttachConfig;
|
||||
}) => {
|
||||
const logger = req.logtail;
|
||||
const logger = req.logger;
|
||||
const { stripeCli } = attachParams;
|
||||
let { curScheduledProduct } = attachParamToCusProducts({ attachParams });
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
type FullCusProduct,
|
||||
} from "@autumn/shared";
|
||||
import { Router } from "express";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripePriceIFNotExist } from "@/external/stripe/createStripePrice/createStripePrice.js";
|
||||
import {
|
||||
createStripeCusIfNotExists,
|
||||
getCusPaymentMethod,
|
||||
} from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import {
|
||||
@@ -140,7 +140,7 @@ export const checkStripeConnections = async ({
|
||||
useCheckout?: boolean;
|
||||
}) => {
|
||||
const { org, customer, products, stripeCus, stripeCli } = attachParams;
|
||||
const logger = req.logtail;
|
||||
const logger = req.logger;
|
||||
const env = customer.env;
|
||||
|
||||
// 2. If invoice only and no email, save email
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FullCustomer, FullProduct } from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { getFreeTrialAfterFingerprint } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import type {
|
||||
AttachParams,
|
||||
InsertCusProductParams,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type AttachBody, ErrCode } from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { toSuccessUrl } from "@/internal/orgs/orgUtils/convertOrgUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductStatus, FullCusProduct, FullCustomer } from "@autumn/shared";
|
||||
import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import {
|
||||
CusProductStatus,
|
||||
type FullCusProduct,
|
||||
type FullCustomer,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getLatestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
|
||||
export const cancelEndOfCycle = async ({
|
||||
req,
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import {
|
||||
AttachScenario,
|
||||
CusProductStatus,
|
||||
FullCusProduct,
|
||||
FullCustomer,
|
||||
cusProductToProduct,
|
||||
type FullCusProduct,
|
||||
type FullCustomer,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { cusProductToProduct } from "@autumn/shared";
|
||||
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
import { activateDefaultProduct } from "../cusProducts/cusProductUtils.js";
|
||||
import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { activateDefaultProduct } from "../cusProducts/cusProductUtils.js";
|
||||
|
||||
export const cancelImmediately = async ({
|
||||
req,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { FullCusProduct, FullCustomer, CusProductStatus } from "@autumn/shared";
|
||||
import {
|
||||
CusProductStatus,
|
||||
type FullCusProduct,
|
||||
type FullCustomer,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
import { cusProductToSchedule } from "../cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ProrationBehavior,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import {
|
||||
CusExpand,
|
||||
FullCusEntWithFullCusProduct,
|
||||
Organization,
|
||||
} from "@autumn/shared";
|
||||
import { AppEnv } from "autumn-js";
|
||||
import { type CusExpand, type Organization } from "@autumn/shared";
|
||||
import type { AppEnv } from "autumn-js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { buildBaseCusCacheKey } from "./cusCacheUtils.js";
|
||||
import { getCusWithCache } from "./getCusWithCache.js";
|
||||
import { initUpstash } from "./upstashUtils.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
|
||||
export const refreshCusCache = async ({
|
||||
db,
|
||||
@@ -43,14 +39,14 @@ export const refreshCusCache = async ({
|
||||
for (const key of list) {
|
||||
const refresh = async () => {
|
||||
const keyName = key;
|
||||
let params = keyName.split(":");
|
||||
let expandParam = params.find((p) => p.startsWith("expand_"));
|
||||
let expand = expandParam
|
||||
const params = keyName.split(":");
|
||||
const expandParam = params.find((p) => p.startsWith("expand_"));
|
||||
const expand = expandParam
|
||||
? expandParam.replace("expand_", "").split(",")
|
||||
: [];
|
||||
|
||||
let entityIdParam = params.find((p) => p.startsWith("entity_"));
|
||||
let entityId = entityIdParam
|
||||
const entityIdParam = params.find((p) => p.startsWith("entity_"));
|
||||
const entityId = entityIdParam
|
||||
? entityIdParam.replace("entity_", "")
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import { logger } from "better-auth";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getEntOptions } from "@/internal/products/prices/priceUtils.js";
|
||||
import { notNullish, nullish } from "@/utils/genUtils.js";
|
||||
import {
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ErrCode } from "@autumn/shared";
|
||||
import { Router } from "express";
|
||||
import { Hono } from "hono";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { CusSearchService } from "@/internal/customers/CusSearchService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
@@ -101,7 +101,7 @@ expressCusRouter.get(
|
||||
org,
|
||||
env: req.env,
|
||||
customer,
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
});
|
||||
|
||||
if (!newCus) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type FullCustomer,
|
||||
type FullProduct,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { addCustomerCreatedTask } from "@/internal/analytics/handlers/handleCustomerCreated.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingIntervalUtils.js";
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import {
|
||||
type AppEnv,
|
||||
CusExpand,
|
||||
type FullCustomer,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getCusPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AppEnv, CusExpand, FullCustomer, Organization } from "@autumn/shared";
|
||||
|
||||
export const getCusPaymentMethodRes = async ({
|
||||
org,
|
||||
@@ -18,12 +22,12 @@ export const getCusPaymentMethodRes = async ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let stripeCli = createStripeCli({
|
||||
const stripeCli = createStripeCli({
|
||||
org,
|
||||
env,
|
||||
});
|
||||
|
||||
let paymentMethod = await getCusPaymentMethod({
|
||||
const paymentMethod = await getCusPaymentMethod({
|
||||
stripeCli,
|
||||
stripeId: fullCus.processor?.id,
|
||||
errorIfNone: false,
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
RewardType,
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
|
||||
export const getCusRewards = async ({
|
||||
org,
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import type Stripe from "stripe";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import { lineItemInCusProduct } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
|
||||
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { stripeDiscountToResponse } from "./stripeDiscountToResponse.js";
|
||||
|
||||
export const getCusUpcomingInvoice = async ({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { RewardService } from "@/internal/rewards/RewardService.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type Stripe from "stripe";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { toSuccessUrl } from "@/internal/orgs/orgUtils/convertOrgUtils.js";
|
||||
@@ -81,7 +81,7 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
org,
|
||||
env: req.env,
|
||||
customer,
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
});
|
||||
|
||||
if (!newCus) {
|
||||
@@ -120,14 +120,14 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
) {
|
||||
try {
|
||||
// Create a default billing portal configuration
|
||||
req.logtail?.info(
|
||||
req.logger?.info(
|
||||
`Creating default billing portal configuration for customer ${customer.id}`,
|
||||
);
|
||||
|
||||
const configuration =
|
||||
await createDefaultBillingPortalConfiguration(stripeCli);
|
||||
|
||||
req.logtail?.info(
|
||||
req.logger?.info(
|
||||
"Successfully created billing portal configuration",
|
||||
{
|
||||
configurationId: configuration.id,
|
||||
@@ -142,13 +142,10 @@ export const handleCreateBillingPortal = async (req: any, res: any) =>
|
||||
configuration: configuration.id,
|
||||
});
|
||||
} catch (configError: any) {
|
||||
req.logtail?.error(
|
||||
"Failed to create billing portal configuration",
|
||||
{
|
||||
error: configError.message,
|
||||
orgId: org.id,
|
||||
},
|
||||
);
|
||||
req.logger?.error("Failed to create billing portal configuration", {
|
||||
error: configError.message,
|
||||
orgId: org.id,
|
||||
});
|
||||
throw new RecaseError({
|
||||
message: `Failed to create billing portal configuration: ${configError.message}`,
|
||||
code: ErrCode.StripeError,
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import {
|
||||
CusProductStatus,
|
||||
cusProductToPrices,
|
||||
ErrCode,
|
||||
type FullCusProduct,
|
||||
type FullCustomer,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import {
|
||||
ACTIVE_STATUSES,
|
||||
CusProductService,
|
||||
} from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import {
|
||||
cancelCusProductSubscriptions,
|
||||
expireAndActivate,
|
||||
fullCusProductToProduct,
|
||||
} from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import {
|
||||
ErrCode,
|
||||
CusProductStatus,
|
||||
FullCusProduct,
|
||||
Organization,
|
||||
AppEnv,
|
||||
FullCustomer,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusService } from "../CusService.js";
|
||||
import { cusProductToPrices } from "@autumn/shared";
|
||||
|
||||
export const expireCusProduct = async ({
|
||||
req,
|
||||
cusProduct, // cus product to expire
|
||||
@@ -61,8 +55,8 @@ export const expireCusProduct = async ({
|
||||
// }
|
||||
|
||||
// 1. If main product, can't expire if there's scheduled product
|
||||
let isMain = !cusProduct.product.is_add_on;
|
||||
let { curScheduledProduct: futureProduct } = getExistingCusProducts({
|
||||
const isMain = !cusProduct.product.is_add_on;
|
||||
const { curScheduledProduct: futureProduct } = getExistingCusProducts({
|
||||
product: cusProduct.product,
|
||||
cusProducts: fullCus.customer_products,
|
||||
internalEntityId: cusProduct.internal_entity_id,
|
||||
@@ -173,7 +167,7 @@ export const handleCusProductExpired = async (req: any, res: any) => {
|
||||
const { db } = req;
|
||||
|
||||
const customerProductId = req.params.customer_product_id;
|
||||
let cusProduct = await CusProductService.get({
|
||||
const cusProduct = await CusProductService.get({
|
||||
db,
|
||||
id: customerProductId,
|
||||
orgId: req.orgId,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { type AppEnv, ErrCode, type Organization } from "@autumn/shared";
|
||||
import chalk from "chalk";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { deleteStripeCustomer } from "@/external/stripe/stripeCusUtils.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { AppEnv, ErrCode, Organization } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
export const deleteCusById = async ({
|
||||
db,
|
||||
@@ -40,7 +43,7 @@ export const deleteCusById = async ({
|
||||
});
|
||||
}
|
||||
|
||||
let response = {
|
||||
const response = {
|
||||
customer,
|
||||
success: true,
|
||||
};
|
||||
@@ -80,7 +83,7 @@ export const handleDeleteCustomer = async (req: any, res: any) =>
|
||||
res,
|
||||
action: "delete customer",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { env, logtail: logger, db, org } = req;
|
||||
const { env, logger, db, org } = req;
|
||||
const { delete_in_stripe } = req.query;
|
||||
|
||||
const data = await deleteCusById({
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type FullCustomer,
|
||||
getStartingBalance,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { getEntOptions } from "@/internal/products/prices/priceUtils.js";
|
||||
import { attachToInsertParams } from "@/internal/products/productUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { getCusEntsInFeatures } from "../cusUtils/cusUtils.js";
|
||||
import { ErrCode, getCusEntBalance } from "@autumn/shared";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import {
|
||||
deductAllowanceFromCusEnt,
|
||||
deductFromUsageBasedCusEnt,
|
||||
} from "@/trigger/updateBalanceTask.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
|
||||
import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { getCusEntsInFeatures } from "../cusUtils/cusUtils.js";
|
||||
|
||||
const getCusFeaturesAndOrg = async (req: any, customerId: string) => {
|
||||
// 1. Get customer
|
||||
@@ -45,7 +41,7 @@ const getCusFeaturesAndOrg = async (req: any, customerId: string) => {
|
||||
|
||||
export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
try {
|
||||
const logger = req.logtail;
|
||||
const logger = req.logger;
|
||||
const cusId = req.params.customer_id;
|
||||
const { env, db, features } = req;
|
||||
const { balances } = req.body;
|
||||
@@ -75,7 +71,7 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
const { cusEnts, cusPrices } = await getCusEntsInFeatures({
|
||||
customer,
|
||||
internalFeatureIds: featuresToUpdate.map((f: any) => f.internal_id!),
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
});
|
||||
|
||||
logger.info("--------------------------------");
|
||||
@@ -121,7 +117,7 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
let { unlimited } = getUnlimitedAndUsageAllowed({
|
||||
const { unlimited } = getUnlimitedAndUsageAllowed({
|
||||
cusEnts,
|
||||
internalFeatureId: feature!.internal_id!,
|
||||
});
|
||||
@@ -135,21 +131,21 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
// Get deductions
|
||||
let newBalance = balance.balance;
|
||||
const newBalance = balance.balance;
|
||||
let curBalance = new Decimal(0);
|
||||
let properties = structuredClone(balance);
|
||||
const properties = structuredClone(balance);
|
||||
delete properties.feature_id;
|
||||
delete properties.balance;
|
||||
|
||||
for (const cusEnt of cusEnts) {
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = balance.interval_count || 1;
|
||||
const cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
const deductionIntCount = balance.interval_count || 1;
|
||||
|
||||
let intCountMatch = notNullish(balance.interval_count)
|
||||
const intCountMatch = notNullish(balance.interval_count)
|
||||
? cusEntIntCount === deductionIntCount
|
||||
: true;
|
||||
|
||||
let intMatch = notNullish(balance.interval)
|
||||
const intMatch = notNullish(balance.interval)
|
||||
? balance.interval === cusEnt.entitlement.interval
|
||||
: true;
|
||||
|
||||
@@ -161,7 +157,7 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
let { balance: cusEntBalance } = getCusEntBalance({
|
||||
const { balance: cusEntBalance } = getCusEntBalance({
|
||||
cusEnt,
|
||||
entityId: balance.entity_id,
|
||||
});
|
||||
@@ -169,7 +165,7 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
curBalance = curBalance.add(new Decimal(cusEntBalance!));
|
||||
}
|
||||
|
||||
let toDeduct = curBalance.sub(newBalance).toNumber();
|
||||
const toDeduct = curBalance.sub(newBalance).toNumber();
|
||||
|
||||
if (toDeduct == 0) {
|
||||
logger.info(`Skipping ${feature!.id} -- no change`);
|
||||
@@ -197,8 +193,8 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
|
||||
const cusEnt = notNullish(interval)
|
||||
? cusEnts.find((cusEnt) => {
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
const cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
const deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
|
||||
return (
|
||||
cusEnt.internal_feature_id === feature!.internal_id! &&
|
||||
@@ -233,14 +229,14 @@ export const handleUpdateBalances = async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
for (const cusEnt of cusEnts) {
|
||||
let cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
let deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
const cusEntIntCount = cusEnt.entitlement.interval_count || 1;
|
||||
const deductionIntCount = featureDeduction.intervalCount || 1;
|
||||
|
||||
let intCountMatch = notNullish(featureDeduction.intervalCount)
|
||||
const intCountMatch = notNullish(featureDeduction.intervalCount)
|
||||
? cusEntIntCount === deductionIntCount
|
||||
: true;
|
||||
|
||||
let intMatch = notNullish(featureDeduction.interval)
|
||||
const intMatch = notNullish(featureDeduction.interval)
|
||||
? featureDeduction.interval === cusEnt.entitlement.interval
|
||||
: true;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CreateCustomerSchema, ErrCode, ProcessorType } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
@@ -139,7 +139,7 @@ export const handleUpdateCustomer = async (req: any, res: any) =>
|
||||
customer: finalCustomer,
|
||||
org,
|
||||
env: req.env,
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
cusProducts: finalCustomer.customer_products,
|
||||
expand: parseCusExpand(req.query.expand as string),
|
||||
features,
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { ErrCode, FullCustomerEntitlement } from "@autumn/shared";
|
||||
import {
|
||||
ErrCode,
|
||||
type FullCustomerEntitlement,
|
||||
getCusEntBalance,
|
||||
} from "@autumn/shared";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||
import { CusPriceService } from "@/internal/customers/cusProducts/cusPrices/CusPriceService.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
|
||||
import { CusPriceService } from "@/internal/customers/cusProducts/cusPrices/CusPriceService.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { getCusEntBalance } from "@autumn/shared";
|
||||
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
|
||||
import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductService } from "../cusProducts/CusProductService.js";
|
||||
|
||||
const getCusOrgAndCusPrice = async ({
|
||||
@@ -41,7 +42,7 @@ const getCusOrgAndCusPrice = async ({
|
||||
|
||||
export const handleUpdateEntitlement = async (req: any, res: any) => {
|
||||
try {
|
||||
const { db, logtail: logger } = req;
|
||||
const { db } = req;
|
||||
const { customer_entitlement_id } = req.params;
|
||||
const { balance, next_reset_at, entity_id } = req.body;
|
||||
|
||||
@@ -96,27 +97,29 @@ export const handleUpdateEntitlement = async (req: any, res: any) => {
|
||||
});
|
||||
}
|
||||
|
||||
let { balance: masterBalance } = getCusEntBalance({
|
||||
const { balance: masterBalance } = getCusEntBalance({
|
||||
cusEnt,
|
||||
entityId: entity_id,
|
||||
});
|
||||
|
||||
const deducted = new Decimal(masterBalance!).minus(balance).toNumber();
|
||||
|
||||
let originalBalance = structuredClone(masterBalance);
|
||||
const originalBalance = structuredClone(masterBalance);
|
||||
|
||||
let { newBalance, newEntities, newAdjustment } = performDeductionOnCusEnt({
|
||||
cusEnt: {
|
||||
...cusEnt,
|
||||
customer_product: cusProduct!,
|
||||
const { newBalance, newEntities, newAdjustment } = performDeductionOnCusEnt(
|
||||
{
|
||||
cusEnt: {
|
||||
...cusEnt,
|
||||
customer_product: cusProduct!,
|
||||
},
|
||||
toDeduct: deducted,
|
||||
addAdjustment: true,
|
||||
allowNegativeBalance: cusEnt.usage_allowed || false,
|
||||
entityId: entity_id,
|
||||
},
|
||||
toDeduct: deducted,
|
||||
addAdjustment: true,
|
||||
allowNegativeBalance: cusEnt.usage_allowed || false,
|
||||
entityId: entity_id,
|
||||
});
|
||||
);
|
||||
|
||||
let updates = {
|
||||
const updates = {
|
||||
balance: newBalance,
|
||||
next_reset_at,
|
||||
entities: newEntities,
|
||||
@@ -130,7 +133,7 @@ export const handleUpdateEntitlement = async (req: any, res: any) => {
|
||||
});
|
||||
|
||||
if (cusPrice && customer) {
|
||||
let fullCusProduct = await CusProductService.get({
|
||||
const fullCusProduct = await CusProductService.get({
|
||||
db,
|
||||
id: cusEnt.customer_product_id,
|
||||
orgId: req.orgId,
|
||||
@@ -150,7 +153,7 @@ export const handleUpdateEntitlement = async (req: any, res: any) => {
|
||||
customer: customer,
|
||||
originalBalance: originalBalance!,
|
||||
newBalance: balance,
|
||||
logger: req.logtail,
|
||||
logger: req.logger,
|
||||
});
|
||||
|
||||
if (newReplaceables && newReplaceables.length > 0) {
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import { Router } from "express";
|
||||
import { CusService } from "./CusService.js";
|
||||
import { ProductService } from "../products/ProductService.js";
|
||||
|
||||
import {
|
||||
CusExpand,
|
||||
CusProductStatus,
|
||||
cusProductToProduct,
|
||||
ErrCode,
|
||||
FullCusProduct, productToCusProduct
|
||||
type FullCusProduct,
|
||||
productToCusProduct,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { Router } from "express";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { CusBatchService } from "../api/batch/CusBatchService.js";
|
||||
import { EventService } from "../api/events/EventService.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { isStripeConnected } from "../orgs/orgUtils.js";
|
||||
import { ProductService } from "../products/ProductService.js";
|
||||
import { mapToProductV2 } from "../products/productV2Utils.js";
|
||||
import { RewardRedemptionService } from "../rewards/RewardRedemptionService.js";
|
||||
import { CusReadService } from "./CusReadService.js";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { cusProductToProduct } from "@autumn/shared";
|
||||
import { isStripeConnected } from "../orgs/orgUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { CusSearchService } from "./CusSearchService.js";
|
||||
import { CusBatchService } from "../api/batch/CusBatchService.js";
|
||||
import { CusService } from "./CusService.js";
|
||||
import { ACTIVE_STATUSES } from "./cusProducts/CusProductService.js";
|
||||
|
||||
export const cusRouter: Router = Router();
|
||||
@@ -128,7 +127,7 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
const { customer_id } = req.params;
|
||||
const orgId = req.orgId;
|
||||
|
||||
let internalCustomer = await CusService.get({
|
||||
const internalCustomer = await CusService.get({
|
||||
db,
|
||||
orgId,
|
||||
env,
|
||||
@@ -144,7 +143,7 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
// Get all redemptions for this customer
|
||||
let [referred, redeemed, stripeCus] = await Promise.all([
|
||||
const [referred, redeemed, stripeCus] = await Promise.all([
|
||||
RewardRedemptionService.getByReferrer({
|
||||
db,
|
||||
internalCustomerId: internalCustomer.internal_id,
|
||||
@@ -169,11 +168,11 @@ cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => {
|
||||
},
|
||||
]);
|
||||
|
||||
let redeemedCustomerIds = redeemed.map(
|
||||
const redeemedCustomerIds = redeemed.map(
|
||||
(redemption: any) => redemption.referral_code.internal_customer_id,
|
||||
);
|
||||
|
||||
let redeemedCustomers = await CusReadService.getInInternalIds({
|
||||
const redeemedCustomers = await CusReadService.getInInternalIds({
|
||||
db,
|
||||
internalIds: redeemedCustomerIds,
|
||||
});
|
||||
@@ -243,7 +242,7 @@ cusRouter.get(
|
||||
"/:customer_id/product/:product_id",
|
||||
async (req: any, res: any) => {
|
||||
try {
|
||||
const { org, env, db, features, logtail: logger } = req;
|
||||
const { org, env, db, features, logger } = req;
|
||||
const { customer_id, product_id } = req.params;
|
||||
const { version, customer_product_id, entity_id } = req.query;
|
||||
|
||||
@@ -270,10 +269,10 @@ cusRouter.get(
|
||||
});
|
||||
}
|
||||
|
||||
let cusProducts = customer.customer_products;
|
||||
let entity = customer.entity;
|
||||
const cusProducts = customer.customer_products;
|
||||
const entity = customer.entity;
|
||||
|
||||
let cusProduct = productToCusProduct({
|
||||
const cusProduct = productToCusProduct({
|
||||
cusProducts,
|
||||
productId: product_id,
|
||||
internalEntityId: entity?.internal_id,
|
||||
@@ -282,7 +281,7 @@ cusRouter.get(
|
||||
inStatuses: ACTIVE_STATUSES,
|
||||
});
|
||||
|
||||
let product = cusProduct
|
||||
const product = cusProduct
|
||||
? cusProductToProduct({ cusProduct })
|
||||
: await ProductService.getFull({
|
||||
db,
|
||||
@@ -295,9 +294,7 @@ cusRouter.get(
|
||||
: undefined,
|
||||
});
|
||||
|
||||
let productV2 = mapToProductV2({ product: product!, features });
|
||||
|
||||
|
||||
const productV2 = mapToProductV2({ product: product!, features });
|
||||
|
||||
res.status(200).json({
|
||||
cusProduct,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { withOrgAuth } from "@/middleware/authMiddleware.js";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import * as crypto from "crypto";
|
||||
import { Router } from "express";
|
||||
import { ApiKeyService } from "./ApiKeyService.js";
|
||||
import { OrgService } from "../orgs/OrgService.js";
|
||||
import { createKey } from "./api-keys/apiKeyUtils.js";
|
||||
import { getSvixDashboardUrl } from "@/external/svix/svixHelpers.js";
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import { CacheManager } from "@/external/caching/CacheManager.js";
|
||||
import { CacheType } from "@/external/caching/cacheActions.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.js";
|
||||
import Stripe from "stripe";
|
||||
import {
|
||||
checkKeyValid,
|
||||
createWebhookEndpoint,
|
||||
} from "@/external/stripe/stripeOnboardingUtils.js";
|
||||
import { getSvixDashboardUrl } from "@/external/svix/svixHelpers.js";
|
||||
import { withOrgAuth } from "@/middleware/authMiddleware.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.js";
|
||||
import { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { OrgService } from "../orgs/OrgService.js";
|
||||
import { clearOrgCache } from "../orgs/orgUtils/clearOrgCache.js";
|
||||
import * as crypto from "crypto";
|
||||
import { isStripeConnected } from "../orgs/orgUtils.js";
|
||||
import { ApiKeyService } from "./ApiKeyService.js";
|
||||
import { createKey } from "./api-keys/apiKeyUtils.js";
|
||||
|
||||
export const devRouter: Router = Router();
|
||||
|
||||
@@ -82,7 +82,7 @@ devRouter.delete("/api_key/:id", withOrgAuth, async (req: any, res) => {
|
||||
const { db, orgId } = req;
|
||||
const { id } = req.params;
|
||||
|
||||
let data = await ApiKeyService.delete({
|
||||
const data = await ApiKeyService.delete({
|
||||
db,
|
||||
id,
|
||||
orgId,
|
||||
@@ -94,8 +94,8 @@ devRouter.delete("/api_key/:id", withOrgAuth, async (req: any, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let batchInvalidate = [];
|
||||
for (let apiKey of data) {
|
||||
const batchInvalidate = [];
|
||||
for (const apiKey of data) {
|
||||
batchInvalidate.push(
|
||||
CacheManager.invalidate({
|
||||
action: CacheType.SecretKey,
|
||||
@@ -240,14 +240,14 @@ export const handleGetOtp = async (req: any, res: any) =>
|
||||
userId: req.user?.id,
|
||||
});
|
||||
|
||||
let org = await OrgService.get({
|
||||
const org = await OrgService.get({
|
||||
db: req.db,
|
||||
orgId: cacheData.orgId,
|
||||
});
|
||||
|
||||
let stripeConnected = isStripeConnected({ org, env: AppEnv.Sandbox });
|
||||
const stripeConnected = isStripeConnected({ org, env: AppEnv.Sandbox });
|
||||
|
||||
let responseData = {
|
||||
const responseData = {
|
||||
...cacheData,
|
||||
stripe_connected: stripeConnected,
|
||||
sandboxKey,
|
||||
@@ -265,9 +265,9 @@ export const handleGetOtp = async (req: any, res: any) =>
|
||||
|
||||
if (!stripeConnected) {
|
||||
// we need to generate a key for the CLI to use.
|
||||
let key = generateRandomKey();
|
||||
const key = generateRandomKey();
|
||||
responseData.stripeFlowAuthKey = key;
|
||||
let stripeCacheData = {
|
||||
const stripeCacheData = {
|
||||
orgId: cacheData.orgId,
|
||||
};
|
||||
await CacheManager.setJson(key, stripeCacheData, OTP_TTL);
|
||||
@@ -283,7 +283,7 @@ devRouter.post("/cli/stripe", async (req: any, res: any) => {
|
||||
res,
|
||||
action: "Get Stripe Flow Auth Key",
|
||||
handler: async () => {
|
||||
const { db, logtail: logger } = req;
|
||||
const { db, logger } = req;
|
||||
const key = req.headers["authorization"];
|
||||
if (!key) {
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
@@ -345,7 +345,7 @@ devRouter.post("/cli/stripe", async (req: any, res: any) => {
|
||||
},
|
||||
});
|
||||
|
||||
let redisClient = await CacheManager.getClient();
|
||||
const redisClient = await CacheManager.getClient();
|
||||
if (!redisClient) {
|
||||
res.status(500).json({ message: "Cache client not initialized" });
|
||||
return;
|
||||
|
||||
@@ -112,7 +112,7 @@ export const handlePostEntityRequest = async (req: any, res: any) =>
|
||||
res,
|
||||
action: "create entity",
|
||||
handler: async (req: any, res: any) => {
|
||||
const { logtail: logger, org } = req;
|
||||
const { logger, org } = req;
|
||||
|
||||
const apiVersion = orgToVersion({
|
||||
org,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import {
|
||||
CusProductStatus,
|
||||
type Entity,
|
||||
type FullCusProduct,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { cancelCurSubs } from "@/internal/customers/change-product/handleDowngrade/cancelCurSubs.js";
|
||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
||||
import { cusProductsToStripeSubs } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { CusProductStatus, Entity, FullCusProduct } from "@autumn/shared";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
export const cancelSubsForEntity = async ({
|
||||
req,
|
||||
@@ -14,10 +18,10 @@ export const cancelSubsForEntity = async ({
|
||||
cusProducts: FullCusProduct[];
|
||||
entity: Entity;
|
||||
}) => {
|
||||
const { org, env, db, logtail: logger } = req;
|
||||
const { org, env, db, logger } = req;
|
||||
try {
|
||||
let stripeCli = createStripeCli({ org, env });
|
||||
let curSubs = await cusProductsToStripeSubs({
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const curSubs = await cusProductsToStripeSubs({
|
||||
cusProducts,
|
||||
stripeCli,
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ export const handleCreateFeature = async (req: any, res: any) => {
|
||||
try {
|
||||
console.log("Trying to create feature");
|
||||
const data = req.body;
|
||||
const { db, orgId, env, logtail: logger } = req;
|
||||
const { db, orgId, env, logger } = req;
|
||||
const parsedFeature = validateFeature(data);
|
||||
|
||||
const feature: Feature = {
|
||||
|
||||
@@ -259,7 +259,7 @@ export const handleUpdateFeature = async (
|
||||
handler: async (req: any, res: any) => {
|
||||
const featureId = req.params.feature_id;
|
||||
const data = req.body;
|
||||
const { db, orgId, env, logtail: logger } = req;
|
||||
const { db, orgId, env, logger } = req;
|
||||
|
||||
// 1. Get feature by ID
|
||||
const features = await FeatureService.getFromReq(req);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Autumn } from "autumn-js";
|
||||
import { autumnHandler } from "autumn-js/express";
|
||||
import { Router } from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { withAuth, withOrgAuth } from "../middleware/authMiddleware.js";
|
||||
import { adminRouter } from "./admin/adminRouter.js";
|
||||
import { withAdminAuth } from "./admin/withAdminAuth.js";
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from "@autumn/shared";
|
||||
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { deleteCusCache } from "@/internal/customers/cusCache/updateCachedCus.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
@@ -56,7 +56,6 @@ export const migrateCustomer = async ({
|
||||
org,
|
||||
features,
|
||||
logger,
|
||||
logtail: logger,
|
||||
timestamp: Date.now(),
|
||||
} as ExtendedRequest;
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
MigrationJobStep,
|
||||
} from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { createStripePriceIFNotExist } from "@/external/stripe/createStripePrice/createStripePrice.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { MigrationService } from "../MigrationService.js";
|
||||
import { migrateCustomer } from "./migrateCustomer.js";
|
||||
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
organizations,
|
||||
user,
|
||||
} from "@autumn/shared";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { and, eq, or, sql } from "drizzle-orm";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { FeatureService } from "../features/FeatureService.js";
|
||||
import { clearOrgCache } from "./orgUtils/clearOrgCache.js";
|
||||
|
||||
export class OrgService {
|
||||
@@ -284,4 +285,54 @@ export class OrgService {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static async getByAccountId({
|
||||
db,
|
||||
accountId,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
accountId: string;
|
||||
}) {
|
||||
const result = await db.query.organizations.findFirst({
|
||||
where: or(
|
||||
eq(
|
||||
sql`${organizations.stripe_connect}->>'default_account_id'`,
|
||||
accountId,
|
||||
),
|
||||
eq(sql`${organizations.stripe_connect}->>'test_account_id'`, accountId),
|
||||
eq(sql`${organizations.stripe_connect}->>'live_account_id'`, accountId),
|
||||
),
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw new RecaseError({
|
||||
message: "Organization not found",
|
||||
code: ErrCode.OrgNotFound,
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const defaultAccountId = result?.stripe_connect?.default_account_id;
|
||||
const testAccountId = result?.stripe_connect?.test_account_id;
|
||||
|
||||
const env =
|
||||
defaultAccountId === accountId || testAccountId === accountId
|
||||
? AppEnv.Sandbox
|
||||
: AppEnv.Live;
|
||||
|
||||
const features = await FeatureService.list({
|
||||
db,
|
||||
orgId: result?.id || "",
|
||||
env,
|
||||
});
|
||||
|
||||
return {
|
||||
features,
|
||||
org: {
|
||||
...(result as Organization),
|
||||
config: OrgConfigSchema.parse(result.config || {}),
|
||||
},
|
||||
env,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { AppEnv, ErrCode } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import { z } from "zod";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { ensureStripeProductsWithEnv } from "@/external/stripe/stripeEnsureUtils.js";
|
||||
import {
|
||||
checkKeyValid,
|
||||
createWebhookEndpoint,
|
||||
} from "@/external/stripe/stripeOnboardingUtils.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { encryptData } from "@/utils/encryptUtils.js";
|
||||
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
import { clearOrgCache } from "../orgUtils/clearOrgCache.js";
|
||||
import { isStripeConnected } from "../orgUtils.js";
|
||||
|
||||
export const connectStripe = async ({
|
||||
@@ -49,6 +48,10 @@ export const connectStripe = async ({
|
||||
test_webhook_secret: encryptData(webhook.secret as string),
|
||||
env,
|
||||
defaultCurrency: account.default_currency,
|
||||
metadata: {
|
||||
org_id: orgId,
|
||||
env: env,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -56,92 +59,14 @@ export const connectStripe = async ({
|
||||
live_webhook_secret: encryptData(webhook.secret as string),
|
||||
env,
|
||||
defaultCurrency: account.default_currency,
|
||||
metadata: {
|
||||
org_id: orgId,
|
||||
env: env,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const connectAllStripe = async ({
|
||||
db,
|
||||
orgId,
|
||||
logger,
|
||||
testApiKey,
|
||||
liveApiKey,
|
||||
defaultCurrency,
|
||||
successUrl,
|
||||
}: {
|
||||
db: any;
|
||||
orgId: string;
|
||||
logger: any;
|
||||
testApiKey: string;
|
||||
liveApiKey: string;
|
||||
defaultCurrency?: string;
|
||||
successUrl: string;
|
||||
}) => {
|
||||
// 1. Check if API keys are valid
|
||||
try {
|
||||
await clearOrgCache({
|
||||
db,
|
||||
orgId,
|
||||
logger,
|
||||
});
|
||||
|
||||
await checkKeyValid(testApiKey);
|
||||
await checkKeyValid(liveApiKey);
|
||||
|
||||
// Get default currency from Stripe
|
||||
const stripe = new Stripe(testApiKey);
|
||||
|
||||
const account = await stripe.accounts.retrieve();
|
||||
|
||||
if (nullish(defaultCurrency) && nullish(account.default_currency)) {
|
||||
throw new RecaseError({
|
||||
message: "Default currency not set",
|
||||
code: ErrCode.StripeKeyInvalid,
|
||||
statusCode: 500,
|
||||
});
|
||||
} else if (nullish(defaultCurrency)) {
|
||||
defaultCurrency = account.default_currency;
|
||||
}
|
||||
} catch (error: any) {
|
||||
// console.error("Error checking stripe keys", error);
|
||||
throw new RecaseError({
|
||||
message: error.message || "Invalid Stripe API keys",
|
||||
code: ErrCode.StripeKeyInvalid,
|
||||
statusCode: 500,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
// 2. Create webhook endpoint
|
||||
let testWebhook: Stripe.WebhookEndpoint;
|
||||
let liveWebhook: Stripe.WebhookEndpoint;
|
||||
try {
|
||||
testWebhook = await createWebhookEndpoint(
|
||||
testApiKey,
|
||||
AppEnv.Sandbox,
|
||||
orgId,
|
||||
);
|
||||
liveWebhook = await createWebhookEndpoint(liveApiKey, AppEnv.Live, orgId);
|
||||
} catch (error) {
|
||||
throw new RecaseError({
|
||||
message: "Error creating stripe webhook",
|
||||
code: ErrCode.StripeKeyInvalid,
|
||||
statusCode: 500,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
defaultCurrency,
|
||||
stripeConfig: {
|
||||
test_api_key: encryptData(testApiKey),
|
||||
live_api_key: encryptData(liveApiKey),
|
||||
test_webhook_secret: encryptData(testWebhook.secret as string),
|
||||
live_webhook_secret: encryptData(liveWebhook.secret as string),
|
||||
success_url: successUrl,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const connectStripeBody = z.object({
|
||||
secret_key: z.string().optional(),
|
||||
success_url: z.string().optional(),
|
||||
@@ -298,7 +223,6 @@ export const handleGetStripe = async (req: any, res: any) => {
|
||||
}
|
||||
|
||||
const stripeCli = createStripeCli({ org, env: req.env });
|
||||
|
||||
const account_details = await stripeCli.accounts.retrieve();
|
||||
|
||||
res.status(200).json(account_details);
|
||||
@@ -1,9 +1,12 @@
|
||||
import { AppEnv, customers, ErrCode, type Organization } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { Response } from "express";
|
||||
import Stripe from "stripe";
|
||||
import { initMasterStripe } from "@/external/connect/initMasterStripe.js";
|
||||
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { deleteSvixApp } from "@/external/svix/svixHelpers.js";
|
||||
import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AppEnv, customers, ErrCode, Organization } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { Response } from "express";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { deleteStripeWebhook } from "../orgUtils.js";
|
||||
|
||||
const deleteSvixWebhooks = async ({
|
||||
@@ -61,12 +64,58 @@ const deleteStripeWebhooks = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const deleteStripeAccounts = async ({
|
||||
org,
|
||||
logger,
|
||||
}: {
|
||||
org: Organization;
|
||||
logger: Logger;
|
||||
}) => {
|
||||
const stripe = initMasterStripe();
|
||||
|
||||
if (org.stripe_connect.test_account_id) {
|
||||
try {
|
||||
await stripe.accounts.del(org.stripe_connect.test_account_id);
|
||||
} catch (error) {
|
||||
if (error instanceof Stripe.errors.StripeError) {
|
||||
logger.error(
|
||||
`Failed to delete stripe test acocunt ID for ${org.id}, ${org.slug}. ${error.message})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (org.stripe_connect.live_account_id) {
|
||||
try {
|
||||
await stripe.accounts.del(org.stripe_connect.live_account_id);
|
||||
} catch (error) {
|
||||
if (error instanceof Stripe.errors.StripeError) {
|
||||
logger.error(
|
||||
`Failed to delete stripe live account ID for ${org.id}, ${org.slug}. ${error.message})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (org.stripe_connect.default_account_id) {
|
||||
try {
|
||||
await stripe.accounts.del(org.stripe_connect.default_account_id);
|
||||
} catch (error) {
|
||||
if (error instanceof Stripe.errors.StripeError) {
|
||||
logger.error(
|
||||
`Failed to delete stripe default account ID for ${org.id}, ${org.slug}. ${error.message})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const handleDeleteOrg = async (req: ExtendedRequest, res: Response) => {
|
||||
try {
|
||||
const { org, db, logtail: logger } = req;
|
||||
const { org, db, logger } = req;
|
||||
|
||||
// 1. Check if any customers
|
||||
let hasCustomers = await db.query.customers.findFirst({
|
||||
const hasCustomers = await db.query.customers.findFirst({
|
||||
where: and(eq(customers.org_id, org.id), eq(customers.env, AppEnv.Live)),
|
||||
});
|
||||
|
||||
@@ -85,8 +134,12 @@ export const handleDeleteOrg = async (req: ExtendedRequest, res: Response) => {
|
||||
logger.info("2. Deleting stripe webhooks");
|
||||
await deleteStripeWebhooks({ org, logger });
|
||||
|
||||
// 4. Delete stripe accounts
|
||||
logger.info("3. Deleting stripe accounts");
|
||||
await deleteStripeAccounts({ org, logger });
|
||||
|
||||
// 4. Delete all sandbox customers
|
||||
logger.info("3. Deleting sandbox customers");
|
||||
logger.info("4. Deleting sandbox customers");
|
||||
await db
|
||||
.delete(customers)
|
||||
.where(
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { clearOrgCache } from "../orgUtils/clearOrgCache.js";
|
||||
import { AppEnv, Organization } from "@autumn/shared";
|
||||
import { isStripeConnected } from "../orgUtils.js";
|
||||
|
||||
export const disconnectStripe = async ({
|
||||
org,
|
||||
env,
|
||||
}: {
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
if (isStripeConnected({ org, env })) {
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const webhooks = await stripeCli.webhookEndpoints.list();
|
||||
for (const webhook of webhooks.data) {
|
||||
if (webhook.url.includes(org.id) && webhook.url.includes(env)) {
|
||||
await stripeCli.webhookEndpoints.del(webhook.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const handleDeleteStripe = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "delete stripe",
|
||||
handler: async (req: any, res: any) => {
|
||||
const org = await OrgService.getFromReq(req);
|
||||
|
||||
let { db, orgId, logtail: logger } = req;
|
||||
await clearOrgCache({
|
||||
db,
|
||||
orgId,
|
||||
logger,
|
||||
});
|
||||
|
||||
try {
|
||||
await disconnectStripe({ org, env: req.env });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to disconnect stripe for ${org.id}, ${org.slug}`, {
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
// Update stripe config:
|
||||
const newStripeConfig = structuredClone(req.org.stripe_config);
|
||||
if (req.env === AppEnv.Sandbox) {
|
||||
newStripeConfig.test_api_key = null;
|
||||
} else {
|
||||
newStripeConfig.live_api_key = null;
|
||||
}
|
||||
|
||||
await OrgService.update({
|
||||
db,
|
||||
orgId: req.orgId,
|
||||
updates: {
|
||||
stripe_config: newStripeConfig,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
message: "Stripe disconnected",
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { createOrgResponse } from "../orgUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
import { createOrgResponse } from "../orgUtils.js";
|
||||
|
||||
export const handleGetOrg = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { session as authSession, member } from "@autumn/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import { OrgService } from "../OrgService.js";
|
||||
import { auth } from "@/utils/auth.js";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { member, session as authSession } from "@autumn/shared";
|
||||
|
||||
export const handleGetOrgMembers = async (req: any, res: any) => {
|
||||
try {
|
||||
@@ -77,7 +76,7 @@ export const handleRemoveMember = async (req: any, res: any) => {
|
||||
);
|
||||
} catch (error) {
|
||||
// Log but don't fail the request if session revocation fails
|
||||
req.logtail?.warn(
|
||||
req.logger?.warn(
|
||||
`Failed to revoke sessions for user ${existingMember.userId} in org ${orgId}:`,
|
||||
error,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { AppEnv, type StripeConfig } from "@autumn/shared";
|
||||
import { z } from "zod/v4";
|
||||
import { ensureStripeProductsWithEnv } from "@/external/stripe/stripeEnsureUtils.js";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { OrgService } from "../../OrgService.js";
|
||||
import { handleStripeSecretKey } from "../../orgUtils/handleStripeSecretKey.js";
|
||||
|
||||
// Connecting stripe
|
||||
const validateConnectStripeRequest = () => {};
|
||||
|
||||
const addSuccessUrlToUpdates = ({
|
||||
success_url,
|
||||
env,
|
||||
configUpdates,
|
||||
}: {
|
||||
success_url?: string;
|
||||
env: AppEnv;
|
||||
configUpdates: StripeConfig;
|
||||
}) => {
|
||||
if (success_url === undefined) return;
|
||||
|
||||
if (env === AppEnv.Sandbox) {
|
||||
configUpdates.sandbox_success_url = success_url;
|
||||
} else {
|
||||
configUpdates.success_url = success_url;
|
||||
}
|
||||
};
|
||||
|
||||
export const handleConnectStripe = createRoute({
|
||||
body: z.object({
|
||||
secret_key: z.string().optional(),
|
||||
success_url: z.string().optional(),
|
||||
default_currency: z.string().optional(),
|
||||
}),
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db, org, logger, env } = ctx;
|
||||
|
||||
const body = c.req.valid("json");
|
||||
const configUpdates: StripeConfig = org.stripe_config || {};
|
||||
|
||||
if (body.secret_key) {
|
||||
const result = await handleStripeSecretKey({
|
||||
orgId: org.id,
|
||||
secretKey: body.secret_key,
|
||||
env,
|
||||
});
|
||||
|
||||
if (env === AppEnv.Sandbox) {
|
||||
configUpdates.test_api_key = result.test_api_key;
|
||||
configUpdates.test_webhook_secret = result.test_webhook_secret;
|
||||
} else {
|
||||
configUpdates.live_api_key = result.live_api_key;
|
||||
configUpdates.live_webhook_secret = result.live_webhook_secret;
|
||||
}
|
||||
}
|
||||
|
||||
addSuccessUrlToUpdates({
|
||||
success_url: body.success_url,
|
||||
env,
|
||||
configUpdates,
|
||||
});
|
||||
|
||||
const newOrg = await OrgService.update({
|
||||
db,
|
||||
orgId: org.id,
|
||||
updates: {
|
||||
default_currency: body.default_currency,
|
||||
stripe_config: configUpdates,
|
||||
},
|
||||
});
|
||||
|
||||
if (newOrg) {
|
||||
await ensureStripeProductsWithEnv({
|
||||
db,
|
||||
logger,
|
||||
req: ctx as ExtendedRequest,
|
||||
org: newOrg,
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({
|
||||
message: "Connect Stripe",
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
AppEnv,
|
||||
type Organization,
|
||||
type StripeConnectConfig,
|
||||
} from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { orgToAccountId } from "@/external/connect/connectUtils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||
import { initMasterStripe } from "@/external/connect/initMasterStripe.js";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { OrgService } from "../../OrgService.js";
|
||||
import { clearOrgCache } from "../../orgUtils/clearOrgCache.js";
|
||||
import { isStripeConnected } from "../../orgUtils.js";
|
||||
|
||||
export const disconnectStripe = async ({
|
||||
org,
|
||||
env,
|
||||
}: {
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
if (isStripeConnected({ org, env, throughSecretKey: true })) {
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const webhooks = await stripeCli.webhookEndpoints.list();
|
||||
for (const webhook of webhooks.data) {
|
||||
if (webhook.url.includes(org.id) && webhook.url.includes(env)) {
|
||||
await stripeCli.webhookEndpoints.del(webhook.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const accountId = orgToAccountId({ org, env, noDefaultAccount: true });
|
||||
|
||||
if (accountId) {
|
||||
const masterStripe = initMasterStripe();
|
||||
await masterStripe.accounts.del(accountId);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearStripeConfig = async ({
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
}: {
|
||||
db: DrizzleCli;
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
const newStripeConfig: any = structuredClone(org.stripe_config) || {};
|
||||
|
||||
if (env === AppEnv.Sandbox) {
|
||||
newStripeConfig.test_api_key = null;
|
||||
} else {
|
||||
newStripeConfig.live_api_key = null;
|
||||
}
|
||||
|
||||
await OrgService.update({
|
||||
db,
|
||||
orgId: org.id,
|
||||
updates: {
|
||||
stripe_config: newStripeConfig,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const handleDeleteStripe = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { db, org, logger, env } = ctx;
|
||||
|
||||
await clearOrgCache({
|
||||
db,
|
||||
orgId: org.id,
|
||||
logger,
|
||||
});
|
||||
|
||||
try {
|
||||
await disconnectStripe({ org, env });
|
||||
} catch (error) {
|
||||
logger.error(`Failed to disconnect stripe for ${org.id}, ${org.slug}`, {
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
// Update stripe config:
|
||||
|
||||
if (isStripeConnected({ org, env, throughSecretKey: true })) {
|
||||
await clearStripeConfig({ db, org, env });
|
||||
} else if (orgToAccountId({ org, env, noDefaultAccount: true })) {
|
||||
const newStripeConnect: StripeConnectConfig =
|
||||
structuredClone(org.stripe_connect) || {};
|
||||
|
||||
if (env === AppEnv.Sandbox) {
|
||||
delete newStripeConnect.test_account_id;
|
||||
} else {
|
||||
delete newStripeConnect.live_account_id;
|
||||
}
|
||||
|
||||
await OrgService.update({
|
||||
db,
|
||||
orgId: org.id,
|
||||
updates: {
|
||||
stripe_connect: newStripeConnect,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import { AppEnv, organizations } from "@autumn/shared";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { Context } from "hono";
|
||||
import { initDrizzle } from "@/db/initDrizzle.js";
|
||||
import { initMasterStripe } from "@/external/connect/initMasterStripe.js";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { clearOrgCache } from "../../orgUtils/clearOrgCache.js";
|
||||
|
||||
export const handleGetOAuthUrl = createRoute({
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { org } = ctx;
|
||||
|
||||
const baseUrl = new URL(
|
||||
`https://connect.stripe.com/oauth/v2/authorize?response_type=code&client_id=${process.env.STRIPE_CLIENT_ID}&scope=read_write`,
|
||||
);
|
||||
|
||||
// Add state + redirect_uri
|
||||
baseUrl.searchParams.set("state", `${org.id}|${ctx.env}`);
|
||||
// baseUrl.searchParams.set(
|
||||
// "redirect_uri",
|
||||
// `${process.env.BETTER_AUTH_URL}/stripe/oauth_callback`,
|
||||
// );
|
||||
baseUrl.searchParams.set(
|
||||
"redirect_uri",
|
||||
`https://express.dev.useautumn.com/stripe/oauth_callback`,
|
||||
);
|
||||
|
||||
return c.json({
|
||||
oauth_url: baseUrl.toString(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const handleOAuthCallback = async (c: Context<HonoEnv>) => {
|
||||
const query = c.req.query();
|
||||
const { code, state, error } = query;
|
||||
|
||||
// Build frontend redirect URL
|
||||
const frontendUrl = process.env.CLIENT_URL || "http://localhost:5173";
|
||||
const redirectUrl = new URL(`${frontendUrl}/developer/configure-stripe`);
|
||||
|
||||
// Handle OAuth error from Stripe
|
||||
if (error) {
|
||||
console.error("Stripe OAuth error:", error);
|
||||
redirectUrl.searchParams.set("error", error);
|
||||
return c.redirect(redirectUrl.toString());
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (!code || !state) {
|
||||
console.error("Missing code or state parameter");
|
||||
redirectUrl.searchParams.set("error", "missing_parameters");
|
||||
return c.redirect(redirectUrl.toString());
|
||||
}
|
||||
|
||||
// Parse state to get orgId and env
|
||||
const [orgId, env] = state.split("|");
|
||||
|
||||
if (!orgId || !env) {
|
||||
console.error("Invalid state format");
|
||||
redirectUrl.searchParams.set("error", "invalid_state");
|
||||
return c.redirect(redirectUrl.toString());
|
||||
}
|
||||
|
||||
console.log(`Org ID: ${orgId}, Env: ${env}, Code: ${code}`);
|
||||
|
||||
try {
|
||||
const stripe = initMasterStripe();
|
||||
|
||||
// Exchange authorization code for access token
|
||||
const response = await stripe.oauth.token({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
});
|
||||
|
||||
const accountId = response.stripe_user_id;
|
||||
console.log("Connected Stripe account:", accountId);
|
||||
|
||||
// Get database connection
|
||||
const { db } = initDrizzle();
|
||||
|
||||
// Fetch the organization
|
||||
const [org] = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(eq(organizations.id, orgId));
|
||||
|
||||
if (!org) {
|
||||
console.error("Organization not found:", orgId);
|
||||
redirectUrl.searchParams.set("error", "org_not_found");
|
||||
return c.redirect(redirectUrl.toString());
|
||||
}
|
||||
|
||||
// Update organization with connected account based on environment
|
||||
const currentConnect = org.stripe_connect || {
|
||||
default_account_id: "",
|
||||
test_account_id: undefined,
|
||||
live_account_id: undefined,
|
||||
};
|
||||
|
||||
const updatedStripeConnect = {
|
||||
...currentConnect,
|
||||
[env === AppEnv.Sandbox ? "test_account_id" : "live_account_id"]:
|
||||
accountId,
|
||||
};
|
||||
|
||||
await db
|
||||
.update(organizations)
|
||||
.set({
|
||||
stripe_connect: updatedStripeConnect,
|
||||
})
|
||||
.where(eq(organizations.id, orgId));
|
||||
|
||||
// Clear organization cache
|
||||
await clearOrgCache({ db, orgId });
|
||||
|
||||
console.log(`Successfully connected Stripe account for org ${orgId}`);
|
||||
|
||||
// Redirect to success
|
||||
redirectUrl.searchParams.set("success", "true");
|
||||
return c.redirect(redirectUrl.toString());
|
||||
} catch (error: unknown) {
|
||||
console.error("Error in OAuth callback:", error);
|
||||
redirectUrl.searchParams.set(
|
||||
"error",
|
||||
error instanceof Error ? error.message : "unknown_error",
|
||||
);
|
||||
return c.redirect(redirectUrl.toString());
|
||||
}
|
||||
};
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { AppEnv, chatResults } from "@autumn/shared";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { parseChatResultFeatures } from "./parseChatFeatures.js";
|
||||
import { parseChatProducts } from "./parseChatProducts.js";
|
||||
import { chatResults } from "@autumn/shared";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { Router } from "express";
|
||||
import { FeatureService } from "@/internal/features/FeatureService.js";
|
||||
import { EntitlementService } from "@/internal/products/entitlements/EntitlementService.js";
|
||||
import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { PriceService } from "@/internal/products/prices/PriceService.js";
|
||||
import { ExtendedRequest, ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { parseChatResultFeatures } from "./parseChatFeatures.js";
|
||||
import { parseChatProducts } from "./parseChatProducts.js";
|
||||
|
||||
export const onboardingRouter: Router = Router();
|
||||
|
||||
@@ -21,7 +22,7 @@ onboardingRouter.post("", async (req: Request, res: any) =>
|
||||
res,
|
||||
action: "onboarding",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { db, logtail: logger, org } = req;
|
||||
const { db, logger, org } = req;
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
@@ -32,7 +33,7 @@ onboardingRouter.post("", async (req: Request, res: any) =>
|
||||
});
|
||||
}
|
||||
|
||||
let chatResult = await db.query.chatResults.findFirst({
|
||||
const chatResult = await db.query.chatResults.findFirst({
|
||||
where: eq(chatResults.id, token),
|
||||
});
|
||||
|
||||
@@ -44,33 +45,33 @@ onboardingRouter.post("", async (req: Request, res: any) =>
|
||||
});
|
||||
}
|
||||
|
||||
let curProducts = await ProductService.listFull({
|
||||
const curProducts = await ProductService.listFull({
|
||||
db,
|
||||
orgId: org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
});
|
||||
|
||||
let curFeatures = await FeatureService.list({
|
||||
const curFeatures = await FeatureService.list({
|
||||
db,
|
||||
orgId: org.id,
|
||||
env: AppEnv.Sandbox,
|
||||
});
|
||||
|
||||
let newProducts = chatResult.data.products.filter((product) => {
|
||||
const newProducts = chatResult.data.products.filter((product) => {
|
||||
return !curProducts.some((p) => p.id === product.id);
|
||||
});
|
||||
|
||||
let newFeatures = chatResult.data.features.filter((feature) => {
|
||||
const newFeatures = chatResult.data.features.filter((feature) => {
|
||||
return !curFeatures.some((f) => f.id === feature.id);
|
||||
});
|
||||
|
||||
if (newFeatures.length > 0 || newProducts.length > 0) {
|
||||
let backendFeatures = parseChatResultFeatures({
|
||||
const backendFeatures = parseChatResultFeatures({
|
||||
features: newFeatures,
|
||||
orgId: org.id,
|
||||
});
|
||||
|
||||
let { products, prices, ents } = await parseChatProducts({
|
||||
const { products, prices, ents } = await parseChatProducts({
|
||||
db,
|
||||
logger,
|
||||
orgId: org.id,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import express, { type Router } from "express";
|
||||
import {
|
||||
handleConnectStripe,
|
||||
handleGetStripe,
|
||||
} from "./handlers/handleConnectStripe.js";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { handleGetStripe } from "./handlers/handleConnectStripe_old.js";
|
||||
import { handleDeleteOrg } from "./handlers/handleDeleteOrg.js";
|
||||
import { handleDeleteStripe } from "./handlers/handleDeleteStripe.js";
|
||||
import { handleGetInvites } from "./handlers/handleGetInvites.js";
|
||||
import { handleGetOrg } from "./handlers/handleGetOrg.js";
|
||||
import {
|
||||
@@ -12,6 +10,9 @@ import {
|
||||
handleRemoveMember,
|
||||
} from "./handlers/handleGetOrgMembers.js";
|
||||
import { handleGetUploadUrl } from "./handlers/handleGetUploadUrl.js";
|
||||
import { handleConnectStripe } from "./handlers/stripeHandlers/handleConnectStripe.js";
|
||||
import { handleDeleteStripe } from "./handlers/stripeHandlers/handleDeleteStripe.js";
|
||||
import { handleGetOAuthUrl } from "./handlers/stripeHandlers/handleGetOAuthUrl.js";
|
||||
|
||||
export const orgRouter: Router = express.Router();
|
||||
orgRouter.get("/members", handleGetOrgMembers);
|
||||
@@ -30,113 +31,10 @@ orgRouter.get("", handleGetOrg);
|
||||
|
||||
orgRouter.get("/stripe", handleGetStripe);
|
||||
|
||||
orgRouter.post("/stripe", handleConnectStripe);
|
||||
// orgRouter.post("/stripe", handleConnectStripe);
|
||||
|
||||
orgRouter.delete("/stripe", handleDeleteStripe);
|
||||
export const honoOrgRouter = new Hono<HonoEnv>();
|
||||
|
||||
// async (req: any, res) => {
|
||||
// try {
|
||||
// let { testApiKey, liveApiKey, successUrl, defaultCurrency } = req.body;
|
||||
// let { db, orgId, logtail: logger } = req;
|
||||
// if (!testApiKey || !liveApiKey || !successUrl) {
|
||||
// throw new RecaseError({
|
||||
// message: "Missing required fields",
|
||||
// code: ErrCode.StripeKeyInvalid,
|
||||
// statusCode: 400,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 1. Check if API keys are valid
|
||||
// try {
|
||||
// await clearOrgCache({
|
||||
// db,
|
||||
// orgId,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// console.log("Connecting Stripe");
|
||||
// await checkKeyValid(testApiKey);
|
||||
// await checkKeyValid(liveApiKey);
|
||||
|
||||
// // Get default currency from Stripe
|
||||
// let stripe = new Stripe(testApiKey);
|
||||
// let account = await stripe.accounts.retrieve();
|
||||
|
||||
// if (nullish(defaultCurrency) && nullish(account.default_currency)) {
|
||||
// throw new RecaseError({
|
||||
// message: "Default currency not set",
|
||||
// code: ErrCode.StripeKeyInvalid,
|
||||
// statusCode: 500,
|
||||
// });
|
||||
// } else if (nullish(defaultCurrency)) {
|
||||
// defaultCurrency = account.default_currency;
|
||||
// }
|
||||
// } catch (error: any) {
|
||||
// throw new RecaseError({
|
||||
// message: error.message || "Invalid Stripe API keys",
|
||||
// code: ErrCode.StripeKeyInvalid,
|
||||
// statusCode: 500,
|
||||
// data: error,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 2. Create webhook endpoint
|
||||
// let testWebhook: Stripe.WebhookEndpoint;
|
||||
// let liveWebhook: Stripe.WebhookEndpoint;
|
||||
// try {
|
||||
// testWebhook = await createWebhookEndpoint(
|
||||
// testApiKey,
|
||||
// AppEnv.Sandbox,
|
||||
// req.orgId
|
||||
// );
|
||||
// liveWebhook = await createWebhookEndpoint(
|
||||
// liveApiKey,
|
||||
// AppEnv.Live,
|
||||
// req.orgId
|
||||
// );
|
||||
// } catch (error) {
|
||||
// throw new RecaseError({
|
||||
// message: "Error creating stripe webhook",
|
||||
// code: ErrCode.StripeKeyInvalid,
|
||||
// statusCode: 500,
|
||||
// data: error,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // 1. Update org in Supabase first
|
||||
// const updatedOrg = await OrgService.update({
|
||||
// db,
|
||||
// orgId: req.orgId,
|
||||
// updates: {
|
||||
// stripe_connected: true,
|
||||
// default_currency: defaultCurrency,
|
||||
// stripe_config: {
|
||||
// test_api_key: encryptData(testApiKey),
|
||||
// live_api_key: encryptData(liveApiKey),
|
||||
// test_webhook_secret: encryptData(testWebhook.secret as string),
|
||||
// live_webhook_secret: encryptData(liveWebhook.secret as string),
|
||||
// success_url: successUrl,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// // 2. Ensure products are created in Stripe (after org is updated)
|
||||
// await ensureStripeProducts({
|
||||
// db,
|
||||
// logger,
|
||||
// req,
|
||||
// org: updatedOrg as Organization,
|
||||
// });
|
||||
|
||||
// res.status(200).json({
|
||||
// message: "Stripe connected",
|
||||
// });
|
||||
// } catch (error: any) {
|
||||
// handleRequestError({
|
||||
// req,
|
||||
// error,
|
||||
// res,
|
||||
// action: "connect stripe",
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
honoOrgRouter.delete("/stripe", ...handleDeleteStripe);
|
||||
honoOrgRouter.post("/stripe", ...handleConnectStripe);
|
||||
honoOrgRouter.get("/stripe/oauth_url", ...handleGetOAuthUrl);
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { decryptData, generatePublishableKey } from "@/utils/encryptUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import {
|
||||
AppEnv,
|
||||
ErrCode,
|
||||
FrontendOrg,
|
||||
Organization,
|
||||
type FrontendOrg,
|
||||
type Organization,
|
||||
type OrgConfig,
|
||||
organizations,
|
||||
OrgConfig,
|
||||
} from "@autumn/shared";
|
||||
import { createStripeCli } from "@/external/stripe/utils.js";
|
||||
import { OrgService } from "./OrgService.js";
|
||||
import { FeatureService } from "../features/FeatureService.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import Stripe from "stripe";
|
||||
import { toSuccessUrl } from "./orgUtils/convertOrgUtils.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { CacheManager } from "@/external/caching/CacheManager.js";
|
||||
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 } 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,
|
||||
@@ -33,7 +34,7 @@ export const shouldReconnectStripe = async ({
|
||||
if (!isStripeConnected({ org, env })) return true;
|
||||
|
||||
try {
|
||||
const stripeCli = createStripeCli({ org, env: env! });
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const newKey = new Stripe(stripeKey);
|
||||
|
||||
const oldAccount = await stripeCli.accounts.retrieve();
|
||||
@@ -49,14 +50,52 @@ export const shouldReconnectStripe = async ({
|
||||
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) {
|
||||
return notNullish(org.stripe_config?.test_api_key);
|
||||
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) {
|
||||
return notNullish(org.stripe_config?.live_api_key);
|
||||
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) &&
|
||||
@@ -90,7 +129,7 @@ export const deleteStripeWebhook = async ({
|
||||
org: Organization;
|
||||
env: AppEnv;
|
||||
}) => {
|
||||
if (!isStripeConnected({ org, env })) return;
|
||||
if (!isStripeConnected({ org, env, throughSecretKey: true })) return;
|
||||
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
const webhookEndpoints = await stripeCli.webhookEndpoints.list({
|
||||
@@ -146,6 +185,19 @@ export const createOrgResponse = ({
|
||||
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";
|
||||
|
||||
return {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
@@ -164,7 +216,8 @@ export const createOrgResponse = ({
|
||||
|
||||
success_url: toSuccessUrl({ org, env }) || "",
|
||||
default_currency: org.default_currency || "usd",
|
||||
stripe_connected: isStripeConnected({ org, env }),
|
||||
stripe_connection: stripeConnection,
|
||||
|
||||
created_at: new Date(org.createdAt).getTime(),
|
||||
test_pkey: org.test_pkey,
|
||||
live_pkey: org.live_pkey,
|
||||
@@ -172,9 +225,9 @@ export const createOrgResponse = ({
|
||||
};
|
||||
|
||||
export const getOrgAndFeatures = async ({ req }: { req: any }) => {
|
||||
let { orgId, env } = req;
|
||||
const { orgId, env } = req;
|
||||
|
||||
let [org, features] = await Promise.all([
|
||||
const [org, features] = await Promise.all([
|
||||
OrgService.getFromReq(req),
|
||||
FeatureService.getFromReq(req),
|
||||
]);
|
||||
|
||||
46
server/src/internal/orgs/orgUtils/createConnectAccount.ts
Normal file
46
server/src/internal/orgs/orgUtils/createConnectAccount.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import "dotenv/config";
|
||||
import type { User } from "better-auth";
|
||||
import type { Organization } from "better-auth/plugins";
|
||||
import { initMasterStripe } from "@/external/connect/initMasterStripe.js";
|
||||
|
||||
export const createConnectAccount = async ({
|
||||
org,
|
||||
user,
|
||||
}: {
|
||||
org: Organization;
|
||||
user: User;
|
||||
}) => {
|
||||
const stripe = initMasterStripe();
|
||||
|
||||
const account = await stripe.accounts.create({
|
||||
business_type: "company",
|
||||
email: user.email,
|
||||
country: "us",
|
||||
company: {
|
||||
name: org.name,
|
||||
},
|
||||
});
|
||||
|
||||
// const account = await stripe.v2.core.accounts.create({
|
||||
// contact_email: user.email,
|
||||
// display_name: org.name,
|
||||
|
||||
// dashboard: "full",
|
||||
|
||||
// identity: {
|
||||
// country: "us",
|
||||
// },
|
||||
|
||||
// configuration: {
|
||||
// merchant: {},
|
||||
// },
|
||||
// defaults: {
|
||||
// responsibilities: {
|
||||
// losses_collector: "stripe",
|
||||
// fees_collector: "stripe",
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
return account;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user