feat: adding checkout router
This commit is contained in:
@@ -126,8 +126,10 @@ export const handleSubscriptionUpdated = async ({
|
||||
subscription.latest_invoice,
|
||||
);
|
||||
|
||||
logger.info("Latest invoice billing reason", latestInvoice.billing_reason);
|
||||
logger.info("Latest invoice status", latestInvoice.status);
|
||||
logger.info(
|
||||
`Latest invoice billing reason: ${latestInvoice.billing_reason}`,
|
||||
);
|
||||
logger.info(`Latest invoice status: ${latestInvoice.status}`);
|
||||
|
||||
if (
|
||||
latestInvoice.status !== "open" ||
|
||||
|
||||
@@ -46,7 +46,7 @@ apiRouter.use("/referrals", referralRouter);
|
||||
apiRouter.use("/redemptions", redemptionRouter);
|
||||
|
||||
// Cus Product
|
||||
apiRouter.use("/attach", attachRouter);
|
||||
apiRouter.use("", attachRouter);
|
||||
apiRouter.use("/cancel", expireRouter);
|
||||
apiRouter.use("/entitled", checkRouter);
|
||||
apiRouter.use("/check", checkRouter);
|
||||
|
||||
@@ -21,14 +21,6 @@ import { notNullish } from "@/utils/genUtils.js";
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
|
||||
const getNextCycle = (preview: AttachPreview) => {
|
||||
if (!preview.due_next_cycle && !preview.due_today) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!preview.due_today && preview.due_next_cycle) {
|
||||
}
|
||||
};
|
||||
export const attachToCheckPreview = async ({
|
||||
preview,
|
||||
params,
|
||||
|
||||
@@ -20,10 +20,12 @@ export const handleCreateCheckout = async ({
|
||||
req,
|
||||
res,
|
||||
attachParams,
|
||||
returnCheckout = false,
|
||||
}: {
|
||||
req: any;
|
||||
res: any;
|
||||
attachParams: AttachParams;
|
||||
returnCheckout?: boolean;
|
||||
}) => {
|
||||
const { db, logtail: logger } = req;
|
||||
|
||||
@@ -113,6 +115,10 @@ export const handleCreateCheckout = async ({
|
||||
|
||||
logger.info(`✅ Successfully created checkout for customer ${customer.id}`);
|
||||
|
||||
if (returnCheckout) {
|
||||
return checkout;
|
||||
}
|
||||
|
||||
let apiVersion = attachParams.apiVersion || APIVersion.v1;
|
||||
if (apiVersion >= APIVersion.v1_1) {
|
||||
res.status(200).json(
|
||||
|
||||
@@ -33,6 +33,8 @@ import { AttachBodySchema } from "./models/AttachBody.js";
|
||||
import { processAttachBody } from "./attachUtils/attachParams/processAttachBody.js";
|
||||
import { handleAttachPreview } from "./handleAttachPreview/handleAttachPreview.js";
|
||||
import { handleAttach } from "./handleAttach.js";
|
||||
import { handleCheckout } from "./checkout/handleCheckout.js";
|
||||
import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils.js";
|
||||
|
||||
export const attachRouter: Router = Router();
|
||||
|
||||
@@ -128,9 +130,13 @@ export const handlePublicAttachErrors = async ({
|
||||
export const checkStripeConnections = async ({
|
||||
req,
|
||||
attachParams,
|
||||
createCus = true,
|
||||
useCheckout = false,
|
||||
}: {
|
||||
req: any;
|
||||
attachParams: AttachParams;
|
||||
createCus?: boolean;
|
||||
useCheckout?: boolean;
|
||||
}) => {
|
||||
const { org, customer, products, stripeCus, stripeCli } = attachParams;
|
||||
const logger = req.logtail;
|
||||
@@ -154,15 +160,18 @@ export const checkStripeConnections = async ({
|
||||
]);
|
||||
}
|
||||
|
||||
const batchProductUpdates = [
|
||||
// createStripeCusIfNotExists({
|
||||
// db: req.db,
|
||||
// org,
|
||||
// env,
|
||||
// customer,
|
||||
// logger,
|
||||
// }),
|
||||
];
|
||||
const batchProductUpdates = [];
|
||||
if (createCus) {
|
||||
batchProductUpdates.push(
|
||||
createStripeCusIfNotExists({
|
||||
db: req.db,
|
||||
org,
|
||||
env,
|
||||
customer,
|
||||
logger,
|
||||
}),
|
||||
);
|
||||
}
|
||||
for (const product of products) {
|
||||
batchProductUpdates.push(
|
||||
checkStripeProductExists({
|
||||
@@ -175,6 +184,13 @@ export const checkStripeConnections = async ({
|
||||
);
|
||||
}
|
||||
await Promise.all(batchProductUpdates);
|
||||
|
||||
await createStripePrices({
|
||||
attachParams,
|
||||
useCheckout,
|
||||
req,
|
||||
logger,
|
||||
});
|
||||
};
|
||||
|
||||
export const createStripePrices = async ({
|
||||
@@ -228,205 +244,6 @@ export const customerHasPm = async ({
|
||||
return notNullOrUndefined(paymentMethod) ? true : false;
|
||||
};
|
||||
|
||||
const handleAttachOld = async (req: any, res: any) =>
|
||||
routeHandler({
|
||||
action: "attach",
|
||||
req,
|
||||
res,
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
await handleAttachRaceCondition({ req, res });
|
||||
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
const logger = req.logtail;
|
||||
|
||||
// PUBLIC STUFF
|
||||
let forceCheckout = req.isPublic || attachBody.force_checkout || false;
|
||||
let isCustom = attachBody.is_custom || false;
|
||||
if (req.isPublic) {
|
||||
isCustom = false;
|
||||
}
|
||||
|
||||
logger.info("--------------------------------");
|
||||
|
||||
const {
|
||||
customer,
|
||||
products,
|
||||
optionsList,
|
||||
prices,
|
||||
entitlements,
|
||||
freeTrial,
|
||||
} = await processAttachBody({
|
||||
req,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
const apiVersion =
|
||||
orgToVersion({
|
||||
org: req.org,
|
||||
reqApiVersion: req.apiVersion,
|
||||
}) || APIVersion.v1;
|
||||
|
||||
const internalEntityId = attachBody.entity_id
|
||||
? customer.entities.find(
|
||||
(e) =>
|
||||
e.id === attachBody.entity_id ||
|
||||
e.internal_id === attachBody.entity_id,
|
||||
)?.internal_id
|
||||
: undefined;
|
||||
|
||||
const stripeCli = createStripeCli({ org: req.org, env: req.env });
|
||||
const paymentMethod = await getCusPaymentMethod({
|
||||
stripeCli,
|
||||
stripeId: customer.processor?.id,
|
||||
});
|
||||
|
||||
// const attachParams: AttachParams = {
|
||||
// stripeCli,
|
||||
// paymentMethod,
|
||||
|
||||
// customer,
|
||||
// products,
|
||||
// optionsList,
|
||||
// prices,
|
||||
// entitlements,
|
||||
// freeTrial,
|
||||
|
||||
// // From req
|
||||
// req,
|
||||
// org: req.org,
|
||||
// entities: customer.entities,
|
||||
// features: req.features,
|
||||
// internalEntityId,
|
||||
// cusProducts: customer.customer_products,
|
||||
|
||||
// // Others
|
||||
// apiVersion,
|
||||
// successUrl: attachBody.success_url,
|
||||
// invoiceOnly: attachBody.invoice_only,
|
||||
// billingAnchor: attachBody.billing_cycle_anchor,
|
||||
// metadata: attachBody.metadata,
|
||||
// disableFreeTrial: attachBody.free_trial === false || false,
|
||||
// checkoutSessionParams: attachBody.checkout_session_params,
|
||||
// isCustom,
|
||||
// };
|
||||
|
||||
// attachParams.apiVersion =
|
||||
// orgToVersion({
|
||||
// org,
|
||||
// reqApiVersion: req.apiVersion,
|
||||
// }) || APIVersion.v1;
|
||||
|
||||
// attachParams.req = req;
|
||||
// attachParams.successUrl = attachBody.success_url;
|
||||
// attachParams.invoiceOnly = attachBody.invoice_only;
|
||||
// attachParams.billingAnchor = attachBody.billing_cycle_anchor;
|
||||
// attachParams.metadata = attachBody.metadata;
|
||||
// attachParams.isCustom = isCustom || false;
|
||||
// attachParams.disableFreeTrial = attachBody.free_trial === false || false;
|
||||
// attachParams.checkoutSessionParams = checkout_session_params;
|
||||
|
||||
// logger.info(
|
||||
// `Customer: ${chalk.yellow(
|
||||
// `${attachParams.customer.id} (${attachParams.customer.name})`,
|
||||
// )}, Products: ${chalk.yellow(
|
||||
// attachParams.products.map((p) => p.id).join(", "),
|
||||
// )}`,
|
||||
// );
|
||||
|
||||
// // 3. Check for stripe connection
|
||||
|
||||
// let hasPm = await customerHasPm({ attachParams });
|
||||
// const useCheckout = !hasPm || forceCheckout;
|
||||
// await createStripePrices({
|
||||
// attachParams,
|
||||
// useCheckout,
|
||||
// req,
|
||||
// logger,
|
||||
// });
|
||||
|
||||
// logger.info(
|
||||
// `Has PM: ${chalk.yellow(hasPm)}, Force Checkout: ${chalk.yellow(
|
||||
// forceCheckout,
|
||||
// )}`,
|
||||
// );
|
||||
// logger.info(
|
||||
// `Use Checkout: ${chalk.yellow(useCheckout)}, Is Custom: ${chalk.yellow(
|
||||
// isCustom,
|
||||
// )}, Invoice Only: ${chalk.yellow(invoiceOnly)}`,
|
||||
// {
|
||||
// details: { hasPm, forceCheckout, useCheckout, isCustom, invoiceOnly },
|
||||
// },
|
||||
// );
|
||||
|
||||
// -------------------- ERROR CHECKING --------------------
|
||||
|
||||
// 1. Check for normal errors (eg. options, different recurring intervals)
|
||||
|
||||
// const { curCusProduct, done } = await handleExistingProduct({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// useCheckout,
|
||||
// invoiceOnly: attachBody.invoice_only,
|
||||
// isCustom,
|
||||
// });
|
||||
|
||||
// await handlePrepaidErrors({
|
||||
// attachParams,
|
||||
// useCheckout,
|
||||
// });
|
||||
|
||||
// await handlePublicAttachErrors({
|
||||
// curCusProduct,
|
||||
// isPublic: req.isPublic || false,
|
||||
// });
|
||||
|
||||
// if (done) return;
|
||||
|
||||
// // -------------------- ATTACH PRODUCT --------------------
|
||||
|
||||
// SCENARIO 1: Free product, no existing product
|
||||
// const newProductsFree = isFreeProduct(attachParams.prices);
|
||||
// const allAddOns = attachParams.products.every((p) => p.is_add_on);
|
||||
|
||||
// if (
|
||||
// (!curCusProduct && newProductsFree) ||
|
||||
// (allAddOns && newProductsFree)
|
||||
// ) {
|
||||
// logger.info("SCENARIO 1: FREE PRODUCT");
|
||||
|
||||
// if (useCheckout && !newProductsFree && !attachParams.invoiceOnly) {
|
||||
// logger.info("SCENARIO 2: USING CHECKOUT");
|
||||
// await handleCreateCheckout({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // SCENARIO 4: Switching product
|
||||
// if (curCusProduct) {
|
||||
// logger.info("SCENARIO 3: SWITCHING PRODUCT");
|
||||
// await handleChangeProduct({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// curCusProduct,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// SCENARIO 5: No existing product, not free product
|
||||
// logger.info("SCENARIO 4: ADDING PRODUCT");
|
||||
// await handleAddProduct({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// });
|
||||
},
|
||||
});
|
||||
|
||||
attachRouter.post("", handleAttach);
|
||||
attachRouter.post("/preview", handleAttachPreview);
|
||||
attachRouter.post("/attach", handleAttach);
|
||||
attachRouter.post("/attach/preview", handleAttachPreview);
|
||||
attachRouter.post("/checkout", handleCheckout);
|
||||
|
||||
115
server/src/internal/customers/attach/checkout/handleCheckout.ts
Normal file
115
server/src/internal/customers/attach/checkout/handleCheckout.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
AttachFunction,
|
||||
AttachScenario,
|
||||
FreeTrialResponseSchema,
|
||||
ProductItemResponseSchema,
|
||||
ProductResponseSchema,
|
||||
} from "@autumn/shared";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { getAttachParams } from "../attachUtils/attachParams/getAttachParams.js";
|
||||
import { AttachBody, AttachBodySchema } from "../models/AttachBody.js";
|
||||
import { ExtendedResponse } from "@/utils/models/Request.js";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { getAttachBranch } from "../attachUtils/getAttachBranch.js";
|
||||
import { getAttachConfig } from "../attachUtils/getAttachConfig.js";
|
||||
import { getAttachFunction } from "../attachUtils/getAttachFunction.js";
|
||||
import { handleCreateCheckout } from "../../add-product/handleCreateCheckout.js";
|
||||
import { z } from "zod";
|
||||
import { checkStripeConnections } from "../attachRouter.js";
|
||||
|
||||
const getAttachVars = async ({
|
||||
req,
|
||||
attachBody,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
attachBody: AttachBody;
|
||||
}) => {
|
||||
const { attachParams } = await getAttachParams({
|
||||
req,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
const branch = await getAttachBranch({
|
||||
req,
|
||||
attachBody,
|
||||
attachParams,
|
||||
fromPreview: true,
|
||||
});
|
||||
|
||||
const { flags, config } = await getAttachConfig({
|
||||
req,
|
||||
attachParams,
|
||||
attachBody,
|
||||
branch,
|
||||
});
|
||||
|
||||
const func = await getAttachFunction({
|
||||
branch,
|
||||
attachParams,
|
||||
attachBody,
|
||||
config,
|
||||
});
|
||||
|
||||
return {
|
||||
attachParams,
|
||||
flags,
|
||||
branch,
|
||||
config,
|
||||
func,
|
||||
};
|
||||
};
|
||||
|
||||
const CheckoutResponseSchema = z.object({
|
||||
url: z.string().nullish(),
|
||||
customer_id: z.string().nullish(),
|
||||
scenario: z.nativeEnum(AttachScenario),
|
||||
lines: z.array(
|
||||
z.object({
|
||||
description: z.string(),
|
||||
amount: z.number(),
|
||||
item: ProductItemResponseSchema,
|
||||
}),
|
||||
),
|
||||
// next_cycle: {
|
||||
// lines
|
||||
// }
|
||||
product: ProductResponseSchema,
|
||||
});
|
||||
|
||||
export const handleCheckout = (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "attach-preview",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { logtail: logger } = req;
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
const { attachParams, flags, branch, config, func } = await getAttachVars(
|
||||
{ req, attachBody },
|
||||
);
|
||||
|
||||
if (func == AttachFunction.CreateCheckout) {
|
||||
await checkStripeConnections({
|
||||
req,
|
||||
attachParams,
|
||||
createCus: true,
|
||||
useCheckout: true,
|
||||
});
|
||||
|
||||
const checkout = await handleCreateCheckout({
|
||||
req,
|
||||
res,
|
||||
attachParams,
|
||||
returnCheckout: true,
|
||||
});
|
||||
|
||||
res.status(200).json(CheckoutResponseSchema.parse(checkout));
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json("ok");
|
||||
|
||||
return;
|
||||
},
|
||||
});
|
||||
@@ -49,13 +49,12 @@ export const handleAttach = async (req: any, res: any) =>
|
||||
config,
|
||||
});
|
||||
|
||||
await checkStripeConnections({ req, attachParams });
|
||||
await createStripePrices({
|
||||
await checkStripeConnections({
|
||||
req,
|
||||
attachParams,
|
||||
useCheckout: config.onlyCheckout,
|
||||
req,
|
||||
logger,
|
||||
});
|
||||
|
||||
await insertCustomItems({
|
||||
db: req.db,
|
||||
customPrices: customPrices || [],
|
||||
|
||||
@@ -32,83 +32,5 @@ export const handleAttachPreview = (req: any, res: any) =>
|
||||
res.status(200).json(attachPreview);
|
||||
|
||||
return;
|
||||
|
||||
// // Handle existing product
|
||||
// const branch = await getAttachBranch({
|
||||
// req,
|
||||
// attachBody,
|
||||
// attachParams,
|
||||
// fromPreview: true,
|
||||
// });
|
||||
|
||||
// const { flags, config } = await getAttachConfig({
|
||||
// req,
|
||||
// attachParams,
|
||||
// attachBody,
|
||||
// branch,
|
||||
// });
|
||||
|
||||
// const func = await getAttachFunction({
|
||||
// branch,
|
||||
// attachParams,
|
||||
// attachBody,
|
||||
// config,
|
||||
// });
|
||||
|
||||
// logger.info("--------------------------------");
|
||||
// logger.info(`ATTACH PREVIEW (org: ${attachParams.org.id})`);
|
||||
// logger.info(`Branch: ${branch}, Function: ${func}`);
|
||||
|
||||
// let now = attachParams.now || Date.now();
|
||||
|
||||
// let preview: any = null;
|
||||
|
||||
// if (
|
||||
// func == AttachFunction.AddProduct ||
|
||||
// func == AttachFunction.CreateCheckout ||
|
||||
// func == AttachFunction.OneOff
|
||||
// ) {
|
||||
// preview = await getNewProductPreview({
|
||||
// attachParams,
|
||||
// now,
|
||||
// logger,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (func == AttachFunction.ScheduleProduct) {
|
||||
// preview = await getDowngradeProductPreview({
|
||||
// attachParams,
|
||||
// now,
|
||||
// logger,
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (
|
||||
// func == AttachFunction.UpgradeDiffInterval ||
|
||||
// func == AttachFunction.UpdatePrepaidQuantity ||
|
||||
// func == AttachFunction.UpgradeSameInterval
|
||||
// ) {
|
||||
// preview = await getUpgradeProductPreview({
|
||||
// req,
|
||||
// attachParams,
|
||||
// branch,
|
||||
// now,
|
||||
// });
|
||||
// }
|
||||
|
||||
// const { curMainProduct, curScheduledProduct } = attachParamToCusProducts({
|
||||
// attachParams,
|
||||
// });
|
||||
|
||||
// res.status(200).json({
|
||||
// branch,
|
||||
// ...preview,
|
||||
// current_product: curMainProduct
|
||||
// ? cusProductToProduct({
|
||||
// cusProduct: curMainProduct,
|
||||
// })
|
||||
// : null,
|
||||
// scheduled_product: curScheduledProduct,
|
||||
// });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -175,6 +175,7 @@ export const getCusProductResponse = async ({
|
||||
canceled_at: cusProduct.canceled_at,
|
||||
is_default: fullProduct.is_default || false,
|
||||
is_add_on: fullProduct.is_add_on || false,
|
||||
version: fullProduct.version,
|
||||
|
||||
// stripe_subscription_ids: cusProduct.subscription_ids || [],
|
||||
started_at: cusProduct.starts_at,
|
||||
|
||||
@@ -12,6 +12,7 @@ export const CusProductResponseSchema = z.object({
|
||||
started_at: z.number(),
|
||||
is_default: z.boolean(),
|
||||
is_add_on: z.boolean(),
|
||||
version: z.number().nullish(),
|
||||
|
||||
stripe_subscription_ids: z.array(z.string()).nullish(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user