Merge pull request #340 from useautumn/checkout-revamp
Checkout revamp: Move checkout to Hono router
This commit is contained in:
@@ -21,8 +21,8 @@
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExportsInTest": "off",
|
||||
"noExplicitAny": "off",
|
||||
"noImplicitAnyLet": "off"
|
||||
"noExplicitAny": "on",
|
||||
"noImplicitAnyLet": "on"
|
||||
},
|
||||
"complexity": {
|
||||
"noStaticOnlyClass": "off"
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { checkToAttachParams } from "@/internal/customers/attach/attachUtils/attachParams/checkToAttachParams.js";
|
||||
import { FullCustomer, FullProduct } from "@autumn/shared";
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AttachBody } from "@autumn/shared";
|
||||
import { attachParamsToPreview } from "@/internal/customers/attach/handleAttachPreview/attachParamsToPreview.js";
|
||||
|
||||
import {
|
||||
type AttachBody,
|
||||
AttachFunction,
|
||||
AttachPreview,
|
||||
CheckProductPreview,
|
||||
Feature,
|
||||
Organization,
|
||||
type AttachPreview,
|
||||
type CheckProductPreview,
|
||||
type Feature,
|
||||
type FullCustomer,
|
||||
type FullProduct,
|
||||
type Organization,
|
||||
} from "@autumn/shared";
|
||||
|
||||
import { getAttachScenario } from "./attachToCheckPreview/getAttachScenario.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { attachParamsToPreview } from "@/internal/billing/attachPreview/attachParamsToPreview.js";
|
||||
import { checkToAttachParams } from "@/internal/customers/attach/attachUtils/attachParams/checkToAttachParams.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { formatAmount } from "@/utils/formatUtils.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import { getAttachScenario } from "./attachToCheckPreview/getAttachScenario.js";
|
||||
|
||||
export const attachToCheckPreview = async ({
|
||||
preview,
|
||||
@@ -39,26 +39,26 @@ export const attachToCheckPreview = async ({
|
||||
fullCus: FullCustomer;
|
||||
}) => {
|
||||
// 1. If check
|
||||
let attachFunc = preview.func;
|
||||
const attachFunc = preview.func;
|
||||
|
||||
if (
|
||||
attachFunc == AttachFunction.AddProduct &&
|
||||
attachFunc === AttachFunction.AddProduct &&
|
||||
isFreeProduct(product.prices)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const noOptions = !preview.options || preview.options.length === 0;
|
||||
if (attachFunc == AttachFunction.CreateCheckout && noOptions) {
|
||||
if (attachFunc === AttachFunction.CreateCheckout && noOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let scenario = await getAttachScenario({
|
||||
const scenario = await getAttachScenario({
|
||||
preview,
|
||||
product,
|
||||
});
|
||||
|
||||
let items = preview.due_today?.line_items?.map((item) => {
|
||||
const items = preview.due_today?.line_items?.map((item) => {
|
||||
return {
|
||||
price: notNullish(item.amount)
|
||||
? formatAmount({
|
||||
@@ -73,21 +73,26 @@ export const attachToCheckPreview = async ({
|
||||
};
|
||||
});
|
||||
|
||||
let options = preview.options?.map((option: any) => {
|
||||
const options = preview.options?.map((option: any) => {
|
||||
return {
|
||||
...option,
|
||||
price: new Decimal(option.price).toDecimalPlaces(2).toNumber(),
|
||||
};
|
||||
});
|
||||
|
||||
let due_today = preview.due_today
|
||||
const due_today = preview.due_today
|
||||
? {
|
||||
price: preview.due_today.total,
|
||||
currency: org.default_currency || "usd",
|
||||
}
|
||||
: undefined;
|
||||
|
||||
let due_next_cycle = undefined;
|
||||
let due_next_cycle:
|
||||
| {
|
||||
price: number;
|
||||
currency: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
if (preview.due_next_cycle) {
|
||||
due_next_cycle = {
|
||||
@@ -101,7 +106,7 @@ export const attachToCheckPreview = async ({
|
||||
};
|
||||
}
|
||||
|
||||
let checkPreview: CheckProductPreview = {
|
||||
const checkPreview: CheckProductPreview = {
|
||||
// title: "Check",
|
||||
// message: "Check",
|
||||
scenario,
|
||||
@@ -158,10 +163,9 @@ export const getProductCheckPreview = async ({
|
||||
};
|
||||
|
||||
const preview = await attachParamsToPreview({
|
||||
req,
|
||||
ctx: req as AutumnContext,
|
||||
attachParams,
|
||||
attachBody,
|
||||
logger,
|
||||
});
|
||||
|
||||
const checkPreview = await attachToCheckPreview({
|
||||
|
||||
@@ -3,43 +3,42 @@ import {
|
||||
AttachBranch,
|
||||
AttachFunction,
|
||||
cusProductToProduct,
|
||||
notNullish,
|
||||
} from "@autumn/shared";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { attachParamToCusProducts } from "../attachUtils/convertAttachParams.js";
|
||||
import { getAttachBranch } from "../attachUtils/getAttachBranch.js";
|
||||
import { getAttachConfig } from "../attachUtils/getAttachConfig.js";
|
||||
import { getAttachFunction } from "../attachUtils/getAttachFunction.js";
|
||||
import { getDowngradeProductPreview } from "./getDowngradeProductPreview.js";
|
||||
import { getMultiAttachPreview } from "./getMultiAttachPreview.js";
|
||||
import { getNewProductPreview } from "./getNewProductPreview.js";
|
||||
import { getUpgradeProductPreview } from "./getUpgradeProductPreview.js";
|
||||
import { attachParamToCusProducts } from "@/internal/customers/attach/attachUtils/convertAttachParams.js";
|
||||
import { getAttachBranch } from "@/internal/customers/attach/attachUtils/getAttachBranch.js";
|
||||
import { getAttachConfig } from "@/internal/customers/attach/attachUtils/getAttachConfig.js";
|
||||
import { getAttachFunction } from "@/internal/customers/attach/attachUtils/getAttachFunction.js";
|
||||
import { getDowngradeProductPreview } from "@/internal/customers/attach/handleAttachPreview/getDowngradeProductPreview.js";
|
||||
import { getMultiAttachPreview } from "@/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.js";
|
||||
import { getNewProductPreview } from "@/internal/customers/attach/handleAttachPreview/getNewProductPreview.js";
|
||||
import { getUpgradeProductPreview } from "@/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import type { AutumnContext } from "../../../honoUtils/HonoEnv";
|
||||
|
||||
export const attachParamsToPreview = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody,
|
||||
logger,
|
||||
withPrepaid = false,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
attachBody: AttachBody;
|
||||
logger: any;
|
||||
withPrepaid?: boolean;
|
||||
}) => {
|
||||
const { logger } = ctx;
|
||||
// Handle existing product
|
||||
|
||||
const branch = await getAttachBranch({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
attachParams,
|
||||
fromPreview: true,
|
||||
});
|
||||
|
||||
const { flags, config } = await getAttachConfig({
|
||||
req,
|
||||
const { config } = await getAttachConfig({
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody,
|
||||
branch,
|
||||
@@ -65,10 +64,9 @@ export const attachParamsToPreview = async ({
|
||||
notNullish(attachParams.productsList)
|
||||
) {
|
||||
preview = await getMultiAttachPreview({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
attachParams,
|
||||
logger,
|
||||
config,
|
||||
branch,
|
||||
});
|
||||
@@ -103,7 +101,7 @@ export const attachParamsToPreview = async ({
|
||||
func === AttachFunction.UpdatePrepaidQuantity
|
||||
) {
|
||||
preview = await getUpgradeProductPreview({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
branch,
|
||||
now,
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "../../honoUtils/HonoEnv.js";
|
||||
import { handleCheckoutV2 } from "./checkout/handleCheckoutV2.js";
|
||||
import { handleSetupPayment } from "./handlers/handleSetupPayment.js";
|
||||
|
||||
export const billingRouter = new Hono<HonoEnv>();
|
||||
|
||||
billingRouter.post("/setup_payment", ...handleSetupPayment);
|
||||
billingRouter.post("/checkout", ...handleCheckoutV2);
|
||||
|
||||
27
server/src/internal/billing/checkout/getHasProrations.ts
Normal file
27
server/src/internal/billing/checkout/getHasProrations.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { AttachBranch, cusProductToPrices } from "@autumn/shared";
|
||||
import { attachParamToCusProducts } from "@/internal/customers/attach/attachUtils/convertAttachParams.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
|
||||
export const getHasProrations = async ({
|
||||
branch,
|
||||
attachParams,
|
||||
}: {
|
||||
branch: AttachBranch;
|
||||
attachParams: AttachParams;
|
||||
}) => {
|
||||
const { curMainProduct } = attachParamToCusProducts({ attachParams });
|
||||
if (branch === AttachBranch.Upgrade) {
|
||||
const curPrices = cusProductToPrices({ cusProduct: curMainProduct! });
|
||||
|
||||
if (!isFreeProduct(curPrices)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (branch === AttachBranch.UpdatePrepaidQuantity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
205
server/src/internal/billing/checkout/handleCheckout.ts
Normal file
205
server/src/internal/billing/checkout/handleCheckout.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
// import {
|
||||
// type AttachBody,
|
||||
// AttachBodySchema,
|
||||
// AttachFunction,
|
||||
// type FeatureOptions,
|
||||
// } from "@autumn/shared";
|
||||
// import { attachParamsToPreview } from "@/internal/billing/attachPreview/attachParamsToPreview.js";
|
||||
// import { handleCreateCheckout } from "@/internal/customers/add-product/handleCreateCheckout.js";
|
||||
// import { handleCreateInvoiceCheckout } from "@/internal/customers/add-product/handleCreateInvoiceCheckout.js";
|
||||
// import {
|
||||
// checkStripeConnections,
|
||||
// handlePrepaidErrors,
|
||||
// } from "@/internal/customers/attach/attachRouter.js";
|
||||
// import { getAttachParams } from "@/internal/customers/attach/attachUtils/attachParams/getAttachParams.js";
|
||||
// import { attachParamsToProduct } from "@/internal/customers/attach/attachUtils/convertAttachParams.js";
|
||||
// import { getAttachBranch } from "@/internal/customers/attach/attachUtils/getAttachBranch.js";
|
||||
// import { getAttachConfig } from "@/internal/customers/attach/attachUtils/getAttachConfig.js";
|
||||
// import { getAttachFunction } from "@/internal/customers/attach/attachUtils/getAttachFunction.js";
|
||||
// import { handleCheckoutErrors } from "@/internal/customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors.js";
|
||||
// import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
// import { priceToFeature } from "@/internal/products/prices/priceUtils/convertPrice.js";
|
||||
// import { isPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
// import { getPriceOptions } from "@/internal/products/prices/priceUtils.js";
|
||||
// import type {
|
||||
// ExtendedRequest,
|
||||
// ExtendedResponse,
|
||||
// } from "@/utils/models/Request.js";
|
||||
// import { routeHandler } from "@/utils/routerUtils.js";
|
||||
// import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
|
||||
// import { getHasProrations } from "./getHasProrations.js";
|
||||
// import { previewToCheckoutRes } from "./previewToCheckoutRes.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 getCheckoutOptions = async ({
|
||||
// req,
|
||||
// attachParams,
|
||||
// }: {
|
||||
// req: ExtendedRequest;
|
||||
// attachParams: AttachParams;
|
||||
// }) => {
|
||||
// const product = attachParamsToProduct({ attachParams });
|
||||
// const prepaidPrices = product.prices.filter((p) =>
|
||||
// isPrepaidPrice({ price: p }),
|
||||
// );
|
||||
|
||||
// const newOptions: FeatureOptions[] = structuredClone(
|
||||
// attachParams.optionsList,
|
||||
// );
|
||||
// for (const prepaidPrice of prepaidPrices) {
|
||||
// const feature = priceToFeature({
|
||||
// price: prepaidPrice,
|
||||
// features: req.features,
|
||||
// });
|
||||
// const option = getPriceOptions(prepaidPrice, attachParams.optionsList);
|
||||
// if (!option) {
|
||||
// newOptions.push({
|
||||
// feature_id: feature?.id ?? "",
|
||||
// internal_feature_id: feature?.internal_id,
|
||||
// quantity: 1,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// attachParams.optionsList = newOptions;
|
||||
// return newOptions;
|
||||
// };
|
||||
|
||||
// export const handleCheckout = (req: any, res: any) =>
|
||||
// routeHandler({
|
||||
// req,
|
||||
// res,
|
||||
// action: "attach-preview",
|
||||
// handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
// const { logger } = req;
|
||||
|
||||
// const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
// const { attachParams, branch, func, config } = await getAttachVars({
|
||||
// req,
|
||||
// attachBody,
|
||||
// });
|
||||
|
||||
// let checkoutUrl = null;
|
||||
|
||||
// handleCheckoutErrors({
|
||||
// attachParams,
|
||||
// branch,
|
||||
// });
|
||||
|
||||
// if (func === AttachFunction.CreateCheckout) {
|
||||
// await checkStripeConnections({
|
||||
// ctx: req as AutumnContext,
|
||||
// attachParams,
|
||||
// createCus: true,
|
||||
// useCheckout: true,
|
||||
// });
|
||||
|
||||
// await handlePrepaidErrors({
|
||||
// attachParams,
|
||||
// config,
|
||||
// useCheckout: config.onlyCheckout,
|
||||
// });
|
||||
|
||||
// if (config.invoiceCheckout) {
|
||||
// const result = await handleCreateInvoiceCheckout({
|
||||
// req,
|
||||
// attachParams,
|
||||
// attachBody,
|
||||
// branch,
|
||||
// config,
|
||||
// });
|
||||
|
||||
// checkoutUrl = result?.invoices?.[0]?.hosted_invoice_url;
|
||||
// } else {
|
||||
// const checkout = await handleCreateCheckout({
|
||||
// req,
|
||||
// res,
|
||||
// attachParams,
|
||||
// config,
|
||||
// returnCheckout: true,
|
||||
// });
|
||||
|
||||
// checkoutUrl = checkout?.url;
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log(`Branch: ${branch}, Func: ${func}`);
|
||||
|
||||
// await getCheckoutOptions({
|
||||
// req,
|
||||
// attachParams,
|
||||
// });
|
||||
|
||||
// const preview = await attachParamsToPreview({
|
||||
// req,
|
||||
// attachParams,
|
||||
// logger,
|
||||
// attachBody,
|
||||
// withPrepaid: true,
|
||||
// });
|
||||
|
||||
// const checkoutRes = await previewToCheckoutRes({
|
||||
// req,
|
||||
// attachParams,
|
||||
// preview,
|
||||
// branch,
|
||||
// });
|
||||
|
||||
// // Get has prorations
|
||||
// const hasProrations = await getHasProrations({
|
||||
// req,
|
||||
// branch,
|
||||
// attachParams,
|
||||
// });
|
||||
|
||||
// res.status(200).json({
|
||||
// ...checkoutRes,
|
||||
// url: checkoutUrl,
|
||||
// has_prorations: hasProrations,
|
||||
// });
|
||||
|
||||
// return;
|
||||
// },
|
||||
// });
|
||||
112
server/src/internal/billing/checkout/handleCheckoutV2.ts
Normal file
112
server/src/internal/billing/checkout/handleCheckoutV2.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
AffectedResource,
|
||||
ApiVersion,
|
||||
AttachFunction,
|
||||
CheckoutParamsV0Schema,
|
||||
} from "@autumn/shared";
|
||||
import { createRoute } from "../../../honoMiddlewares/routeHandler";
|
||||
import type { ExtendedRequest } from "../../../utils/models/Request";
|
||||
import { handleCreateCheckout } from "../../customers/add-product/handleCreateCheckout";
|
||||
import { handleCreateInvoiceCheckout } from "../../customers/add-product/handleCreateInvoiceCheckout";
|
||||
import {
|
||||
checkStripeConnections,
|
||||
handlePrepaidErrors,
|
||||
} from "../../customers/attach/attachRouter";
|
||||
import { handleCheckoutErrors } from "../../customers/attach/attachUtils/handleAttachErrors/handleCheckoutErrors";
|
||||
import { attachParamsToPreview } from "../attachPreview/attachParamsToPreview";
|
||||
import { getHasProrations } from "./getHasProrations";
|
||||
import { previewToCheckoutRes } from "./previewToCheckoutRes";
|
||||
import { checkoutToAttachContext } from "./utils/checkoutToAttachContext";
|
||||
import { getCheckoutOptions } from "./utils/getCheckoutOptions";
|
||||
|
||||
export const handleCheckoutV2 = createRoute({
|
||||
versionedBody: {
|
||||
latest: CheckoutParamsV0Schema,
|
||||
[ApiVersion.V1_Beta]: CheckoutParamsV0Schema,
|
||||
},
|
||||
resource: AffectedResource.Checkout,
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const body = c.req.valid("json");
|
||||
|
||||
const { attachParams, branch, func, config } =
|
||||
await checkoutToAttachContext({
|
||||
ctx,
|
||||
checkoutParams: body,
|
||||
});
|
||||
|
||||
let checkoutUrl = null;
|
||||
|
||||
handleCheckoutErrors({
|
||||
attachParams,
|
||||
branch,
|
||||
});
|
||||
|
||||
if (func === AttachFunction.CreateCheckout) {
|
||||
await checkStripeConnections({
|
||||
ctx,
|
||||
attachParams,
|
||||
createCus: true,
|
||||
useCheckout: true,
|
||||
});
|
||||
|
||||
await handlePrepaidErrors({
|
||||
attachParams,
|
||||
config,
|
||||
useCheckout: config.onlyCheckout,
|
||||
});
|
||||
|
||||
if (config.invoiceCheckout) {
|
||||
const result = await handleCreateInvoiceCheckout({
|
||||
req: ctx as ExtendedRequest,
|
||||
attachParams,
|
||||
attachBody: body,
|
||||
branch,
|
||||
config,
|
||||
});
|
||||
|
||||
checkoutUrl = result?.invoices?.[0]?.hosted_invoice_url;
|
||||
} else {
|
||||
const checkout = await handleCreateCheckout({
|
||||
req: ctx as ExtendedRequest,
|
||||
attachParams,
|
||||
config,
|
||||
returnCheckout: true,
|
||||
});
|
||||
|
||||
checkoutUrl = checkout?.url;
|
||||
}
|
||||
}
|
||||
|
||||
await getCheckoutOptions({
|
||||
ctx,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
const preview = await attachParamsToPreview({
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody: body,
|
||||
withPrepaid: true,
|
||||
});
|
||||
|
||||
const checkoutRes = await previewToCheckoutRes({
|
||||
req: ctx as ExtendedRequest,
|
||||
attachParams,
|
||||
preview,
|
||||
branch,
|
||||
});
|
||||
|
||||
// Get has prorations
|
||||
const hasProrations = await getHasProrations({
|
||||
branch,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
...checkoutRes,
|
||||
url: checkoutUrl,
|
||||
has_prorations: hasProrations,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type AttachBranch,
|
||||
type AttachPreview,
|
||||
type CheckoutLine,
|
||||
CheckoutResponseSchema,
|
||||
type CheckoutLineV0,
|
||||
CheckoutResponseV0Schema,
|
||||
cusProductToEnts,
|
||||
cusProductToPrices,
|
||||
cusProductToProduct,
|
||||
@@ -12,6 +12,11 @@ import {
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
attachParamsToProduct,
|
||||
attachParamToCusProducts,
|
||||
} from "@/internal/customers/attach/attachUtils/convertAttachParams.js";
|
||||
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
|
||||
import { getPriceEntitlement } from "@/internal/products/prices/priceUtils.js";
|
||||
import { isPriceItem } from "@/internal/products/product-items/productItemUtils/getItemType.js";
|
||||
import {
|
||||
@@ -20,11 +25,6 @@ import {
|
||||
} from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import {
|
||||
attachParamsToProduct,
|
||||
attachParamToCusProducts,
|
||||
} from "../attachUtils/convertAttachParams.js";
|
||||
|
||||
export const previewToCheckoutRes = async ({
|
||||
req,
|
||||
@@ -52,7 +52,7 @@ export const previewToCheckoutRes = async ({
|
||||
const newEnts = attachParams.entitlements;
|
||||
const allPrices = [...curPrices, ...newPrices];
|
||||
const allEnts = [...curEnts, ...newEnts];
|
||||
let lines: CheckoutLine[] = [];
|
||||
let lines: CheckoutLineV0[] = [];
|
||||
|
||||
if (preview.due_today && preview.due_today.line_items.length > 0) {
|
||||
lines = preview.due_today.line_items
|
||||
@@ -77,7 +77,7 @@ export const previewToCheckoutRes = async ({
|
||||
}),
|
||||
};
|
||||
})
|
||||
.filter(notNullish) as CheckoutLine[];
|
||||
.filter(notNullish) as CheckoutLineV0[];
|
||||
}
|
||||
|
||||
const curProduct = curCusProduct
|
||||
@@ -172,7 +172,7 @@ export const previewToCheckoutRes = async ({
|
||||
})
|
||||
.filter(notNullish);
|
||||
|
||||
return CheckoutResponseSchema.parse({
|
||||
return CheckoutResponseV0Schema.parse({
|
||||
customer_id: attachParams.customer.id,
|
||||
lines,
|
||||
product: newProduct,
|
||||
@@ -0,0 +1,61 @@
|
||||
import type {
|
||||
AttachBranch,
|
||||
AttachConfig,
|
||||
AttachFunction,
|
||||
CheckoutParamsV0,
|
||||
} from "@autumn/shared";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv";
|
||||
import { getAttachParams } from "../../../customers/attach/attachUtils/attachParams/getAttachParams";
|
||||
import { getAttachBranch } from "../../../customers/attach/attachUtils/getAttachBranch";
|
||||
import { getAttachConfig } from "../../../customers/attach/attachUtils/getAttachConfig";
|
||||
import { getAttachFunction } from "../../../customers/attach/attachUtils/getAttachFunction";
|
||||
import type { AttachFlags } from "../../../customers/attach/models/AttachFlags";
|
||||
import type { AttachParams } from "../../../customers/cusProducts/AttachParams";
|
||||
|
||||
export const checkoutToAttachContext = async ({
|
||||
ctx,
|
||||
checkoutParams,
|
||||
}: {
|
||||
ctx: AutumnContext;
|
||||
checkoutParams: CheckoutParamsV0;
|
||||
}): Promise<{
|
||||
attachParams: AttachParams;
|
||||
flags: AttachFlags;
|
||||
branch: AttachBranch;
|
||||
config: AttachConfig;
|
||||
func: AttachFunction;
|
||||
}> => {
|
||||
const { attachParams } = await getAttachParams({
|
||||
ctx,
|
||||
attachBody: checkoutParams,
|
||||
});
|
||||
|
||||
const branch = await getAttachBranch({
|
||||
ctx,
|
||||
attachBody: checkoutParams,
|
||||
attachParams,
|
||||
fromPreview: true,
|
||||
});
|
||||
|
||||
const { flags, config } = await getAttachConfig({
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody: checkoutParams,
|
||||
branch,
|
||||
});
|
||||
|
||||
const func = await getAttachFunction({
|
||||
branch,
|
||||
attachParams,
|
||||
attachBody: checkoutParams,
|
||||
config,
|
||||
});
|
||||
|
||||
return {
|
||||
attachParams,
|
||||
flags,
|
||||
branch,
|
||||
config,
|
||||
func,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { type FeatureOptions, isPrepaidPrice } from "@autumn/shared";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv";
|
||||
import { attachParamsToProduct } from "../../../customers/attach/attachUtils/convertAttachParams";
|
||||
import type { AttachParams } from "../../../customers/cusProducts/AttachParams";
|
||||
import { getPriceOptions } from "../../../products/prices/priceUtils";
|
||||
import { priceToFeature } from "../../../products/prices/priceUtils/convertPrice";
|
||||
|
||||
export const getCheckoutOptions = async ({
|
||||
ctx,
|
||||
attachParams,
|
||||
}: {
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
}) => {
|
||||
const product = attachParamsToProduct({ attachParams });
|
||||
const prepaidPrices = product.prices.filter((p) =>
|
||||
isPrepaidPrice({ price: p }),
|
||||
);
|
||||
|
||||
const newOptions: FeatureOptions[] = structuredClone(
|
||||
attachParams.optionsList,
|
||||
);
|
||||
for (const prepaidPrice of prepaidPrices) {
|
||||
const feature = priceToFeature({
|
||||
price: prepaidPrice,
|
||||
features: ctx.features,
|
||||
});
|
||||
const option = getPriceOptions(prepaidPrice, attachParams.optionsList);
|
||||
if (!option) {
|
||||
newOptions.push({
|
||||
feature_id: feature?.id ?? "",
|
||||
internal_feature_id: feature?.internal_id,
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attachParams.optionsList = newOptions;
|
||||
return newOptions;
|
||||
};
|
||||
@@ -27,7 +27,7 @@ export const handleCreateCheckout = async ({
|
||||
returnCheckout = false,
|
||||
}: {
|
||||
req: any;
|
||||
res: any;
|
||||
res?: any;
|
||||
attachParams: AttachParams;
|
||||
config: AttachConfig;
|
||||
returnCheckout?: boolean;
|
||||
@@ -182,7 +182,7 @@ export const handleCreateCheckout = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (returnCheckout) {
|
||||
if (returnCheckout || !res) {
|
||||
return checkout;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import {
|
||||
AttachParams,
|
||||
AttachResultSchema,
|
||||
} from "../cusProducts/AttachParams.js";
|
||||
|
||||
import { createCheckoutMetadata } from "@/internal/metadata/metadataUtils.js";
|
||||
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
|
||||
import { handlePaidProduct } from "../attach/attachFunctions/addProductFlow/handlePaidProduct.js";
|
||||
import {
|
||||
AttachBody,
|
||||
AttachBranch,
|
||||
AttachConfig,
|
||||
type AttachBody,
|
||||
type AttachBranch,
|
||||
type AttachConfig,
|
||||
SuccessCode,
|
||||
} from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import type Stripe from "stripe";
|
||||
import { createCheckoutMetadata } from "@/internal/metadata/metadataUtils.js";
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import { handleOneOffFunction } from "../attach/attachFunctions/addProductFlow/handleOneOffFunction.js";
|
||||
import { handlePaidProduct } from "../attach/attachFunctions/addProductFlow/handlePaidProduct.js";
|
||||
import { handleMultiAttachFlow } from "../attach/attachFunctions/multiAttach/handleMultiAttachFlow.js";
|
||||
import {
|
||||
type AttachParams,
|
||||
AttachResultSchema,
|
||||
} from "../cusProducts/AttachParams.js";
|
||||
|
||||
export const handleCreateInvoiceCheckout = async ({
|
||||
req,
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { notNullish, nullOrUndefined } from "@/utils/genUtils.js";
|
||||
import { handleCheckout } from "./checkout/handleCheckout.js";
|
||||
import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
|
||||
import { handleAttach } from "./handleAttach.js";
|
||||
import { handleAttachPreview } from "./handleAttachPreview/handleAttachPreview.js";
|
||||
|
||||
@@ -125,18 +125,18 @@ export const handlePublicAttachErrors = async ({
|
||||
};
|
||||
|
||||
export const checkStripeConnections = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
createCus = true,
|
||||
useCheckout = false,
|
||||
}: {
|
||||
req: any;
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
createCus?: boolean;
|
||||
useCheckout?: boolean;
|
||||
}) => {
|
||||
const { org, customer, products, stripeCus, stripeCli } = attachParams;
|
||||
const logger = req.logger;
|
||||
const { logger, db } = ctx;
|
||||
const env = customer.env;
|
||||
|
||||
// 2. If invoice only and no email, save email
|
||||
@@ -144,7 +144,7 @@ export const checkStripeConnections = async ({
|
||||
customer.email = `${customer.id}-${org.id}@invoices.useautumn.com`;
|
||||
await Promise.all([
|
||||
CusService.update({
|
||||
db: req.db,
|
||||
db,
|
||||
idOrInternalId: customer.internal_id,
|
||||
orgId: org.id,
|
||||
env,
|
||||
@@ -164,7 +164,7 @@ export const checkStripeConnections = async ({
|
||||
if (createCus) {
|
||||
batchProductUpdates.push(
|
||||
createStripeCusIfNotExists({
|
||||
db: req.db,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
customer,
|
||||
@@ -176,7 +176,7 @@ export const checkStripeConnections = async ({
|
||||
for (const product of products) {
|
||||
batchProductUpdates.push(
|
||||
checkStripeProductExists({
|
||||
db: req.db,
|
||||
db,
|
||||
org,
|
||||
env,
|
||||
product,
|
||||
@@ -189,23 +189,24 @@ export const checkStripeConnections = async ({
|
||||
await createStripePrices({
|
||||
attachParams,
|
||||
useCheckout,
|
||||
req,
|
||||
ctx,
|
||||
logger,
|
||||
});
|
||||
};
|
||||
|
||||
export const createStripePrices = async ({
|
||||
ctx,
|
||||
attachParams,
|
||||
useCheckout,
|
||||
req,
|
||||
logger,
|
||||
}: {
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
useCheckout: boolean;
|
||||
req: any;
|
||||
logger: any;
|
||||
}) => {
|
||||
const { prices, entitlements, products, org, stripeCli } = attachParams;
|
||||
const { db } = ctx;
|
||||
|
||||
const batchPriceUpdates = [];
|
||||
|
||||
@@ -216,7 +217,7 @@ export const createStripePrices = async ({
|
||||
|
||||
batchPriceUpdates.push(
|
||||
createStripePriceIFNotExist({
|
||||
db: req.db,
|
||||
db,
|
||||
stripeCli,
|
||||
price,
|
||||
entitlements,
|
||||
@@ -250,4 +251,4 @@ export const customerHasPm = async ({
|
||||
|
||||
attachRouter.post("/attach", handleAttach);
|
||||
attachRouter.post("/attach/preview", handleAttachPreview);
|
||||
attachRouter.post("/checkout", handleCheckout);
|
||||
// attachRouter.post("/checkout", handleCheckout);
|
||||
|
||||
@@ -11,16 +11,15 @@ import { ProductService } from "@/internal/products/ProductService.js";
|
||||
import { isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
|
||||
import type { AutumnContext } from "../../../../../../honoUtils/HonoEnv";
|
||||
import { getExistingCusProducts } from "../../../../cusProducts/cusProductUtils/getExistingCusProducts";
|
||||
|
||||
const getProductsForAttach = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
}) => {
|
||||
const {
|
||||
@@ -31,9 +30,9 @@ const getProductsForAttach = async ({
|
||||
} = attachBody;
|
||||
|
||||
const products = await ProductService.listFull({
|
||||
db: req.db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
db: ctx.db,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
inIds: inputProducts
|
||||
? inputProducts.map((p) => p.product_id)
|
||||
: product_ids || [product_id!],
|
||||
@@ -74,15 +73,15 @@ const getProductsForAttach = async ({
|
||||
};
|
||||
|
||||
export const getCustomerAndProducts = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
}) => {
|
||||
const [customer, products] = await Promise.all([
|
||||
getOrCreateCustomer({
|
||||
ctx: req as unknown as AutumnContext,
|
||||
ctx,
|
||||
customerId: attachBody.customer_id,
|
||||
customerData: {
|
||||
...attachBody.customer_data,
|
||||
@@ -96,7 +95,7 @@ export const getCustomerAndProducts = async ({
|
||||
entityId: attachBody.entity_id || undefined,
|
||||
entityData: attachBody.entity_data || undefined,
|
||||
}),
|
||||
getProductsForAttach({ req, attachBody }),
|
||||
getProductsForAttach({ ctx, attachBody }),
|
||||
]);
|
||||
|
||||
// if customer is on product v3, products[0] should just be the customer's product if version isn't explicitly passed in.
|
||||
@@ -109,13 +108,11 @@ export const getCustomerAndProducts = async ({
|
||||
internalEntityId: customer.entity?.internal_id,
|
||||
});
|
||||
|
||||
// console.log(
|
||||
// `Product ${product.id} (${product.version}), curSameProduct: ${curSameProduct?.product.id} (version: ${curSameProduct?.product.version})`,
|
||||
// );
|
||||
if (curSameProduct) {
|
||||
products[i] = cusProductToProduct({ cusProduct: curSameProduct });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { customer, products };
|
||||
};
|
||||
|
||||
@@ -16,22 +16,22 @@ import {
|
||||
} from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
import { handleNewProductItems } from "@/internal/products/product-items/productItemUtils/handleNewProductItems.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../../../honoUtils/HonoEnv.js";
|
||||
import { mapOptionsList } from "../../mapOptionsList.js";
|
||||
|
||||
export const getPricesAndEnts = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
customer,
|
||||
products,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
customer: FullCustomer;
|
||||
products: FullProduct[];
|
||||
}) => {
|
||||
const { options: optionsInput, is_custom, free_trial } = attachBody;
|
||||
const { features, db, org, logger } = req;
|
||||
const { features, db, org, logger } = ctx;
|
||||
|
||||
const { curMainProduct, curSameProduct } = getExistingCusProducts({
|
||||
product: products[0],
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { AttachBody } from "@autumn/shared";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
|
||||
import type { ExtendedRequest } from "../../../../../utils/models/Request.js";
|
||||
import type { AttachParams } from "../../../cusProducts/AttachParams.js";
|
||||
import { processAttachBody } from "./processAttachBody.js";
|
||||
|
||||
export const getAttachParams = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
}) => {
|
||||
const {
|
||||
@@ -23,7 +24,7 @@ export const getAttachParams = async ({
|
||||
stripeVars,
|
||||
rewards,
|
||||
} = await processAttachBody({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
@@ -51,10 +52,11 @@ export const getAttachParams = async ({
|
||||
replaceables: [],
|
||||
rewards,
|
||||
// From req
|
||||
req,
|
||||
org: req.org,
|
||||
req: ctx as ExtendedRequest,
|
||||
|
||||
org: ctx.org,
|
||||
entities: customer.entities,
|
||||
features: req.features,
|
||||
features: ctx.features,
|
||||
internalEntityId,
|
||||
entityId: entityId || undefined,
|
||||
cusProducts: customer.customer_products,
|
||||
@@ -72,7 +74,7 @@ export const getAttachParams = async ({
|
||||
setupPayment: attachBody.setup_payment,
|
||||
|
||||
// Others
|
||||
apiVersion: req.apiVersion.value,
|
||||
apiVersion: ctx.apiVersion.value,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,17 +3,17 @@ import type Stripe from "stripe";
|
||||
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";
|
||||
import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
|
||||
import { getCustomerAndProducts } from "./attachParamsUtils/getCusAndProducts.js";
|
||||
import { getPricesAndEnts } from "./attachParamsUtils/getPricesAndEnts.js";
|
||||
import { getStripeCusData } from "./attachParamsUtils/getStripeCusData.js";
|
||||
|
||||
export const getRewards = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
stripeCli,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
stripeCli: Stripe;
|
||||
}) => {
|
||||
@@ -31,10 +31,10 @@ export const getRewards = async ({
|
||||
|
||||
// 1. Get reward by id or promo code
|
||||
const rewards = await RewardService.getByIdOrCode({
|
||||
db: req.db,
|
||||
db: ctx.db,
|
||||
codes: rewardArray,
|
||||
orgId: req.org.id,
|
||||
env: req.env,
|
||||
orgId: ctx.org.id,
|
||||
env: ctx.env,
|
||||
});
|
||||
|
||||
for (const reward of rewardArray) {
|
||||
@@ -62,33 +62,33 @@ export const getRewards = async ({
|
||||
};
|
||||
|
||||
export const processAttachBody = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
}) => {
|
||||
// 1. Get customer and products
|
||||
const { org, env, logger } = req;
|
||||
const { org, env, logger } = ctx;
|
||||
|
||||
const stripeCli = createStripeCli({ org, env });
|
||||
|
||||
const { customer, products } = await getCustomerAndProducts({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
const [stripeCusData, rewardData] = await Promise.all([
|
||||
getStripeCusData({
|
||||
stripeCli,
|
||||
db: req.db,
|
||||
db: ctx.db,
|
||||
org,
|
||||
env,
|
||||
customer,
|
||||
logger,
|
||||
}),
|
||||
getRewards({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
stripeCli,
|
||||
}),
|
||||
@@ -104,7 +104,7 @@ export const processAttachBody = async ({
|
||||
customPrices,
|
||||
customEnts,
|
||||
} = await getPricesAndEnts({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
customer,
|
||||
products,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
isProductUpgrade,
|
||||
} from "@/internal/products/productUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js";
|
||||
import { isMainTrialBranch } from "./attachUtils.js";
|
||||
@@ -291,12 +291,13 @@ const getChangeProductBranch = async ({
|
||||
};
|
||||
|
||||
export const getAttachBranch = async ({
|
||||
req,
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: might be used in the future
|
||||
ctx,
|
||||
attachBody,
|
||||
attachParams,
|
||||
fromPreview,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
attachParams: AttachParams;
|
||||
fromPreview?: boolean;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { notNullish, nullish } from "@/utils/genUtils.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { willMergeSub } from "../mergeUtils/mergeUtils.js";
|
||||
import type { AttachFlags } from "../models/AttachFlags.js";
|
||||
@@ -55,12 +56,12 @@ export const intervalsAreSame = ({
|
||||
};
|
||||
|
||||
export const getAttachConfig = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody,
|
||||
branch,
|
||||
}: {
|
||||
req: any;
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
attachBody: AttachBody;
|
||||
branch: AttachBranch;
|
||||
@@ -68,7 +69,7 @@ export const getAttachConfig = async ({
|
||||
const { org, prices, paymentMethod } = attachParams;
|
||||
|
||||
const flags: AttachFlags = {
|
||||
isPublic: req.isPublic,
|
||||
isPublic: ctx.isPublic,
|
||||
forceCheckout: attachBody.force_checkout || false,
|
||||
invoiceOnly: attachParams.invoiceOnly || false,
|
||||
isFree: isFreeProduct(prices),
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { AttachBranch } from "@autumn/shared";
|
||||
import {
|
||||
attachParamsToCurCusProduct,
|
||||
attachParamToCusProducts,
|
||||
} from "../attachUtils/convertAttachParams.js";
|
||||
import { cusProductToPrices } from "@autumn/shared";
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
|
||||
export const getHasProrations = async ({
|
||||
req,
|
||||
branch,
|
||||
attachParams,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
branch: AttachBranch;
|
||||
attachParams: AttachParams;
|
||||
}) => {
|
||||
let hasProrations = false;
|
||||
|
||||
let { curMainProduct } = attachParamToCusProducts({ attachParams });
|
||||
if (branch == AttachBranch.Upgrade) {
|
||||
let curPrices = cusProductToPrices({ cusProduct: curMainProduct! });
|
||||
|
||||
if (!isFreeProduct(curPrices)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (branch == AttachBranch.UpdatePrepaidQuantity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -1,204 +0,0 @@
|
||||
import {
|
||||
type AttachBody,
|
||||
AttachBodySchema,
|
||||
AttachFunction,
|
||||
type FeatureOptions,
|
||||
} from "@autumn/shared";
|
||||
import { priceToFeature } from "@/internal/products/prices/priceUtils/convertPrice.js";
|
||||
import { isPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js";
|
||||
import { getPriceOptions } from "@/internal/products/prices/priceUtils.js";
|
||||
import type {
|
||||
ExtendedRequest,
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import { handleCreateCheckout } from "../../add-product/handleCreateCheckout.js";
|
||||
import { handleCreateInvoiceCheckout } from "../../add-product/handleCreateInvoiceCheckout.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import {
|
||||
checkStripeConnections,
|
||||
handlePrepaidErrors,
|
||||
} from "../attachRouter.js";
|
||||
import { getAttachParams } from "../attachUtils/attachParams/getAttachParams.js";
|
||||
import { attachParamsToProduct } from "../attachUtils/convertAttachParams.js";
|
||||
import { getAttachBranch } from "../attachUtils/getAttachBranch.js";
|
||||
import { getAttachConfig } from "../attachUtils/getAttachConfig.js";
|
||||
import { getAttachFunction } from "../attachUtils/getAttachFunction.js";
|
||||
import { handleCheckoutErrors } from "../attachUtils/handleAttachErrors/handleCheckoutErrors.js";
|
||||
import { attachParamsToPreview } from "../handleAttachPreview/attachParamsToPreview.js";
|
||||
import { getHasProrations } from "./getHasProrations.js";
|
||||
import { previewToCheckoutRes } from "./previewToCheckoutRes.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 getCheckoutOptions = async ({
|
||||
req,
|
||||
attachParams,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
attachParams: AttachParams;
|
||||
}) => {
|
||||
const product = attachParamsToProduct({ attachParams });
|
||||
const prepaidPrices = product.prices.filter((p) =>
|
||||
isPrepaidPrice({ price: p }),
|
||||
);
|
||||
|
||||
const newOptions: FeatureOptions[] = structuredClone(
|
||||
attachParams.optionsList,
|
||||
);
|
||||
for (const prepaidPrice of prepaidPrices) {
|
||||
const feature = priceToFeature({
|
||||
price: prepaidPrice,
|
||||
features: req.features,
|
||||
});
|
||||
const option = getPriceOptions(prepaidPrice, attachParams.optionsList);
|
||||
if (!option) {
|
||||
newOptions.push({
|
||||
feature_id: feature?.id ?? "",
|
||||
internal_feature_id: feature?.internal_id,
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attachParams.optionsList = newOptions;
|
||||
return newOptions;
|
||||
};
|
||||
|
||||
export const handleCheckout = (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "attach-preview",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { logger, features } = req;
|
||||
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
const { attachParams, branch, func, config } = await getAttachVars({
|
||||
req,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
let checkoutUrl = null;
|
||||
|
||||
handleCheckoutErrors({
|
||||
attachParams,
|
||||
branch,
|
||||
});
|
||||
|
||||
if (func === AttachFunction.CreateCheckout) {
|
||||
await checkStripeConnections({
|
||||
req,
|
||||
attachParams,
|
||||
createCus: true,
|
||||
useCheckout: true,
|
||||
});
|
||||
|
||||
await handlePrepaidErrors({
|
||||
attachParams,
|
||||
config,
|
||||
useCheckout: config.onlyCheckout,
|
||||
});
|
||||
|
||||
if (config.invoiceCheckout) {
|
||||
const result = await handleCreateInvoiceCheckout({
|
||||
req,
|
||||
attachParams,
|
||||
attachBody,
|
||||
branch,
|
||||
config,
|
||||
});
|
||||
|
||||
checkoutUrl = result?.invoices?.[0]?.hosted_invoice_url;
|
||||
} else {
|
||||
const checkout = await handleCreateCheckout({
|
||||
req,
|
||||
res,
|
||||
attachParams,
|
||||
config,
|
||||
returnCheckout: true,
|
||||
});
|
||||
|
||||
checkoutUrl = checkout?.url;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Branch: ${branch}, Func: ${func}`);
|
||||
|
||||
await getCheckoutOptions({
|
||||
req,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
const preview = await attachParamsToPreview({
|
||||
req,
|
||||
attachParams,
|
||||
logger,
|
||||
attachBody,
|
||||
withPrepaid: true,
|
||||
});
|
||||
|
||||
const checkoutRes = await previewToCheckoutRes({
|
||||
req,
|
||||
attachParams,
|
||||
preview,
|
||||
branch,
|
||||
});
|
||||
|
||||
// Get has prorations
|
||||
const hasProrations = await getHasProrations({
|
||||
req,
|
||||
branch,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
...checkoutRes,
|
||||
url: checkoutUrl,
|
||||
has_prorations: hasProrations,
|
||||
});
|
||||
|
||||
return;
|
||||
},
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
|
||||
import { checkStripeConnections } from "./attachRouter.js";
|
||||
import { getAttachParams } from "./attachUtils/attachParams/getAttachParams.js";
|
||||
import { getAttachBranch } from "./attachUtils/getAttachBranch.js";
|
||||
@@ -23,20 +24,22 @@ export const handleAttach = async (req: any, res: any) =>
|
||||
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
const ctx = req as AutumnContext;
|
||||
|
||||
const { attachParams, customPrices, customEnts } = await getAttachParams({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
// Handle existing product
|
||||
const branch = await getAttachBranch({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
attachParams,
|
||||
});
|
||||
|
||||
const { flags, config } = await getAttachConfig({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody,
|
||||
branch,
|
||||
@@ -51,7 +54,7 @@ export const handleAttach = async (req: any, res: any) =>
|
||||
});
|
||||
|
||||
await checkStripeConnections({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
useCheckout: config.onlyCheckout,
|
||||
});
|
||||
@@ -82,7 +85,7 @@ export const handleAttach = async (req: any, res: any) =>
|
||||
freeTrial: attachParams.freeTrial,
|
||||
},
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (_error) {}
|
||||
|
||||
await runAttachFunction({
|
||||
req,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
type AttachBody,
|
||||
type AttachBranch,
|
||||
type AttachConfig,
|
||||
cusProductsToPrices,
|
||||
type PreviewLineItem,
|
||||
} from "@autumn/shared";
|
||||
@@ -9,7 +10,7 @@ import type Stripe from "stripe";
|
||||
import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
|
||||
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
|
||||
import { notNullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import { getAddAndRemoveProducts } from "../attachFunctions/multiAttach/getAddAndRemoveProducts.js";
|
||||
import { priceToNewPreviewItem } from "../attachPreviewUtils/priceToNewPreviewItem.js";
|
||||
@@ -18,18 +19,17 @@ import { getCustomerSub } from "../attachUtils/convertAttachParams.js";
|
||||
import { handleMultiAttachErrors } from "../attachUtils/handleAttachErrors/handleMultiAttachErrors.js";
|
||||
|
||||
export const getMultiAttachPreview = async ({
|
||||
req,
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: might be used in the future
|
||||
ctx,
|
||||
attachBody,
|
||||
attachParams,
|
||||
logger,
|
||||
config,
|
||||
branch,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachBody: AttachBody;
|
||||
attachParams: AttachParams;
|
||||
logger: any;
|
||||
config: any;
|
||||
config: AttachConfig;
|
||||
branch: AttachBranch;
|
||||
}) => {
|
||||
await handleMultiAttachErrors({ attachParams, attachBody, branch });
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getNextStartOfMonthUnix } from "@/internal/products/prices/billingInter
|
||||
import { getAlignedUnix } from "@/internal/products/prices/billingIntervalUtils2.js";
|
||||
import { getLargestInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
|
||||
import { mapToProductItems } from "@/internal/products/productV2Utils.js";
|
||||
import type { Logger } from "../../../../external/logtail/logtailUtils.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import {
|
||||
attachParamsToProduct,
|
||||
@@ -86,7 +87,7 @@ export const getNewProductPreview = async ({
|
||||
}: {
|
||||
branch: AttachBranch;
|
||||
attachParams: AttachParams;
|
||||
logger: any;
|
||||
logger: Logger;
|
||||
config: AttachConfig;
|
||||
withPrepaid?: boolean;
|
||||
}) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { isPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePrice
|
||||
import { isFreeProduct } from "@/internal/products/productUtils.js";
|
||||
import { mapToProductItems } from "@/internal/products/productV2Utils.js";
|
||||
import { nullish } from "@/utils/genUtils.js";
|
||||
import type { ExtendedRequest } from "@/utils/models/Request.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import type { AttachParams } from "../../cusProducts/AttachParams.js";
|
||||
import {
|
||||
attachParamsToProduct,
|
||||
@@ -132,21 +132,21 @@ const filterNoProratePrepaidItems = ({
|
||||
};
|
||||
|
||||
export const getUpgradeProductPreview = async ({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
branch,
|
||||
now,
|
||||
withPrepaid = false,
|
||||
config,
|
||||
}: {
|
||||
req: ExtendedRequest;
|
||||
ctx: AutumnContext;
|
||||
attachParams: AttachParams;
|
||||
branch: AttachBranch;
|
||||
now: number;
|
||||
withPrepaid?: boolean;
|
||||
config: AttachConfig;
|
||||
}) => {
|
||||
const { logger } = req;
|
||||
const { logger } = ctx;
|
||||
|
||||
const { curMainProduct, curSameProduct } = attachParamToCusProducts({
|
||||
attachParams,
|
||||
|
||||
@@ -4,30 +4,29 @@ import type {
|
||||
ExtendedResponse,
|
||||
} from "@/utils/models/Request.js";
|
||||
import { routeHandler } from "@/utils/routerUtils.js";
|
||||
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
|
||||
import { attachParamsToPreview } from "../../../billing/attachPreview/attachParamsToPreview.js";
|
||||
import { getAttachParams } from "../attachUtils/attachParams/getAttachParams.js";
|
||||
|
||||
import { attachParamsToPreview } from "./attachParamsToPreview.js";
|
||||
|
||||
export const handleAttachPreview = (req: any, res: any) =>
|
||||
routeHandler({
|
||||
req,
|
||||
res,
|
||||
action: "attach-preview",
|
||||
handler: async (req: ExtendedRequest, res: ExtendedResponse) => {
|
||||
const { logger } = req;
|
||||
const attachBody = AttachBodySchema.parse(req.body);
|
||||
|
||||
// console.log("attachBody", attachBody);
|
||||
const ctx = req as AutumnContext;
|
||||
const { attachParams } = await getAttachParams({
|
||||
req,
|
||||
ctx,
|
||||
attachBody,
|
||||
});
|
||||
|
||||
const attachPreview = await attachParamsToPreview({
|
||||
req,
|
||||
ctx,
|
||||
attachParams,
|
||||
attachBody,
|
||||
logger,
|
||||
});
|
||||
|
||||
res.status(200).json(attachPreview);
|
||||
|
||||
@@ -54,7 +54,13 @@ export const handleMigrateProductV2 = createRoute({
|
||||
env,
|
||||
});
|
||||
|
||||
if (currentMigrations.length > 0) {
|
||||
if (
|
||||
currentMigrations.find(
|
||||
(m) =>
|
||||
m.from_internal_product_id === fromProduct.internal_id &&
|
||||
m.to_internal_product_id === toProduct.internal_id,
|
||||
)
|
||||
) {
|
||||
throw new RecaseError({
|
||||
message: "Another migration is ongoing, cannot create a new migration",
|
||||
});
|
||||
|
||||
@@ -6,13 +6,15 @@ import { expectFeaturesCorrect } from "@tests/utils/expectUtils/expectFeaturesCo
|
||||
import { expectInvoiceAfterUsage } from "@tests/utils/expectUtils/expectSingleUse/expectUsageInvoice.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { timeout } from "@/utils/genUtils.js";
|
||||
import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceToNextInvoice } from "../../utils/testAttachUtils/testAttachUtils";
|
||||
import { hoursToFinalizeInvoice } from "../../utils/constants";
|
||||
import { advanceTestClock } from "../../utils/stripeUtils";
|
||||
|
||||
const testCase = "aentity2";
|
||||
|
||||
@@ -115,9 +117,20 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach pro annual to
|
||||
});
|
||||
|
||||
test("should have correct invoice after cycle", async () => {
|
||||
curUnix = await advanceToNextInvoice({
|
||||
// curUnix = await advanceToNextInvoice({
|
||||
// stripeCli: ctx.stripeCli,
|
||||
// testClockId,
|
||||
// });
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addMonths(new Date(), 1).getTime(),
|
||||
});
|
||||
|
||||
curUnix = await advanceTestClock({
|
||||
stripeCli: ctx.stripeCli,
|
||||
testClockId,
|
||||
advanceTo: addHours(curUnix, hoursToFinalizeInvoice).getTime(),
|
||||
});
|
||||
|
||||
await expectInvoiceAfterUsage({
|
||||
|
||||
@@ -149,7 +149,6 @@ describe(`${chalk.yellowBright(`attach/${testCase}: Testing attach pro diff enti
|
||||
product: pro,
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
||||
const entity2Usage = Math.random() * 1000000;
|
||||
test("should track usage on entity 2", async () => {
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { type Customer, LegacyVersion, OnDecrease, OnIncrease } from "@autumn/shared";
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import type Stripe from "stripe";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import {
|
||||
type Customer,
|
||||
LegacyVersion,
|
||||
OnDecrease,
|
||||
OnIncrease,
|
||||
} from "@autumn/shared";
|
||||
import { TestFeature } from "@tests/setup/v2Features.js";
|
||||
import { hoursToFinalizeInvoice } from "@tests/utils/constants.js";
|
||||
import { attachAndExpectCorrect } from "@tests/utils/expectUtils/expectAttach.js";
|
||||
import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js";
|
||||
import ctx from "@tests/utils/testInitUtils/createTestContext.js";
|
||||
import chalk from "chalk";
|
||||
import { addHours, addMonths } from "date-fns";
|
||||
import { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||
import { getMainCusProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
|
||||
import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem.js";
|
||||
import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js";
|
||||
import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js";
|
||||
import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js";
|
||||
|
||||
const testCase = "prepaid1";
|
||||
|
||||
@@ -154,9 +158,7 @@ describe(`${chalk.yellowBright(`attach/${testCase}: update quantity, no proratio
|
||||
});
|
||||
|
||||
const autumnCus = await autumn.customers.get(customerId);
|
||||
expect(autumnCus.features[TestFeature.Messages].balance).toBe(
|
||||
newQuantity,
|
||||
);
|
||||
expect(autumnCus.features[TestFeature.Messages].balance).toBe(newQuantity);
|
||||
|
||||
expect(autumnCus.invoices.length).toBe(3);
|
||||
expect(autumnCus.invoices[0].total).toBe((newQuantity / 100) * 12.5);
|
||||
|
||||
19
shared/api/billing/checkout/checkoutParams.ts
Normal file
19
shared/api/billing/checkout/checkoutParams.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// import { z } from "zod/v4";
|
||||
// import {
|
||||
// AttachBodySchema,
|
||||
// ExtAttachBodySchema,
|
||||
// } from "../../core/attachModels.js";
|
||||
|
||||
// export const ExtCheckoutParamsSchema = ExtAttachBodySchema.extend({
|
||||
// setup_payment: z.boolean().optional(),
|
||||
// }).meta({
|
||||
// description:
|
||||
// "Returns a Stripe Checkout URL for the customer to make a payment, or returns payment confirmation information.",
|
||||
// });
|
||||
|
||||
// export const CheckoutParamsSchema = AttachBodySchema.extend({
|
||||
// // skip_checkout: z.boolean().optional(),
|
||||
// setup_payment: z.boolean().optional(),
|
||||
// });
|
||||
|
||||
// export type CheckoutParams = z.infer<typeof CheckoutParamsSchema>;
|
||||
32
shared/api/billing/checkout/checkoutResponse.ts
Normal file
32
shared/api/billing/checkout/checkoutResponse.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
|
||||
// import { z } from "zod/v4";
|
||||
// import { ApiPlanSchema } from "../../products/apiPlan.js";
|
||||
|
||||
// // RESULT
|
||||
// export const CheckoutLineSchema = z.object({
|
||||
// description: z.string(),
|
||||
// amount: z.number(),
|
||||
// // item: ApiProductItemSchema.nullish(),
|
||||
// });
|
||||
|
||||
// export const CheckoutResponseSchema = z.object({
|
||||
// url: z.string().nullish(),
|
||||
// customer_id: z.string(),
|
||||
// lines: z.array(CheckoutLineSchema),
|
||||
|
||||
// plan: ApiPlanSchema.nullish(),
|
||||
// current_plan: ApiPlanSchema.nullish(),
|
||||
|
||||
// options: z.array(FeatureOptionsSchema).nullish(),
|
||||
// total: z.number().nullish(),
|
||||
// currency: z.string().nullish(),
|
||||
// has_prorations: z.boolean().nullish(),
|
||||
// next_cycle: z
|
||||
// .object({
|
||||
// starts_at: z.number(),
|
||||
// total: z.number(),
|
||||
// })
|
||||
// .nullish(),
|
||||
// });
|
||||
|
||||
// export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
43
shared/api/billing/checkout/prevVersions/checkoutParamsV0.ts
Normal file
43
shared/api/billing/checkout/prevVersions/checkoutParamsV0.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
AttachBodySchema,
|
||||
ExtAttachBodySchema,
|
||||
} from "@api/core/attachModels.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const ExtCheckoutParamsV0Schema = ExtAttachBodySchema.extend({
|
||||
setup_payment: z.boolean().optional(),
|
||||
}).meta({
|
||||
description:
|
||||
"Returns a Stripe Checkout URL for the customer to make a payment, or returns payment confirmation information.",
|
||||
});
|
||||
|
||||
export const CheckoutParamsV0Schema = AttachBodySchema.extend({
|
||||
setup_payment: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// // RESULT
|
||||
// export const CheckoutLineV0Schema = z.object({
|
||||
// description: z.string(),
|
||||
// amount: z.number(),
|
||||
// item: ApiProductItemSchema.nullish(),
|
||||
// });
|
||||
|
||||
// export const CheckoutResponseV0Schema = z.object({
|
||||
// url: z.string().nullish(),
|
||||
// customer_id: z.string(),
|
||||
// lines: z.array(CheckoutLineV0Schema),
|
||||
// product: ApiProductSchema.nullish(),
|
||||
// current_product: ApiProductSchema.nullish(),
|
||||
// options: z.array(FeatureOptionsSchema).nullish(),
|
||||
// total: z.number().nullish(),
|
||||
// currency: z.string().nullish(),
|
||||
// has_prorations: z.boolean().nullish(),
|
||||
// next_cycle: z
|
||||
// .object({
|
||||
// starts_at: z.number(),
|
||||
// total: z.number(),
|
||||
// })
|
||||
// .nullish(),
|
||||
// });
|
||||
|
||||
export type CheckoutParamsV0 = z.infer<typeof CheckoutParamsV0Schema>;
|
||||
@@ -4,16 +4,16 @@ import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.
|
||||
import { z } from "zod/v4";
|
||||
|
||||
// RESULT
|
||||
export const CheckoutLineSchema = z.object({
|
||||
export const CheckoutLineV0Schema = z.object({
|
||||
description: z.string(),
|
||||
amount: z.number(),
|
||||
item: ApiProductItemSchema.nullish(),
|
||||
});
|
||||
|
||||
export const CheckoutResponseSchema = z.object({
|
||||
export const CheckoutResponseV0Schema = z.object({
|
||||
url: z.string().nullish(),
|
||||
customer_id: z.string(),
|
||||
lines: z.array(CheckoutLineSchema),
|
||||
lines: z.array(CheckoutLineV0Schema),
|
||||
|
||||
product: ApiProductSchema.nullish(),
|
||||
current_product: ApiProductSchema.nullish(),
|
||||
@@ -30,4 +30,5 @@ export const CheckoutResponseSchema = z.object({
|
||||
.nullish(),
|
||||
});
|
||||
|
||||
export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
export type CheckoutLineV0 = z.infer<typeof CheckoutLineV0Schema>;
|
||||
export type CheckoutResponseV0 = z.infer<typeof CheckoutResponseV0Schema>;
|
||||
@@ -1,45 +1,45 @@
|
||||
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
import { ApiProductSchema } from "@api/products/previousVersions/apiProduct.js";
|
||||
import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
|
||||
import { z } from "zod/v4";
|
||||
import { AttachBodySchema, ExtAttachBodySchema } from "./attachModels.js";
|
||||
// import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
|
||||
// import { ApiProductSchema } from "@api/products/previousVersions/apiProduct.js";
|
||||
// import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
|
||||
// import { z } from "zod/v4";
|
||||
// import { AttachBodySchema, ExtAttachBodySchema } from "./attachModels.js";
|
||||
|
||||
export const ExtCheckoutParamsSchema = ExtAttachBodySchema.extend({
|
||||
setup_payment: z.boolean().optional(),
|
||||
}).meta({
|
||||
description:
|
||||
"Returns a Stripe Checkout URL for the customer to make a payment, or returns payment confirmation information.",
|
||||
});
|
||||
// export const ExtCheckoutParamsSchema = ExtAttachBodySchema.extend({
|
||||
// setup_payment: z.boolean().optional(),
|
||||
// }).meta({
|
||||
// description:
|
||||
// "Returns a Stripe Checkout URL for the customer to make a payment, or returns payment confirmation information.",
|
||||
// });
|
||||
|
||||
export const CheckoutParamsSchema = AttachBodySchema.extend({
|
||||
// skip_checkout: z.boolean().optional(),
|
||||
setup_payment: z.boolean().optional(),
|
||||
});
|
||||
// export const CheckoutParamsSchema = AttachBodySchema.extend({
|
||||
// // skip_checkout: z.boolean().optional(),
|
||||
// setup_payment: z.boolean().optional(),
|
||||
// });
|
||||
|
||||
// RESULT
|
||||
export const CheckoutLineSchema = z.object({
|
||||
description: z.string(),
|
||||
amount: z.number(),
|
||||
item: ApiProductItemSchema.nullish(),
|
||||
});
|
||||
// // RESULT
|
||||
// export const CheckoutLineSchema = z.object({
|
||||
// description: z.string(),
|
||||
// amount: z.number(),
|
||||
// item: ApiProductItemSchema.nullish(),
|
||||
// });
|
||||
|
||||
export const CheckoutResponseSchema = z.object({
|
||||
url: z.string().nullish(),
|
||||
customer_id: z.string(),
|
||||
lines: z.array(CheckoutLineSchema),
|
||||
product: ApiProductSchema.nullish(),
|
||||
current_product: ApiProductSchema.nullish(),
|
||||
options: z.array(FeatureOptionsSchema).nullish(),
|
||||
total: z.number().nullish(),
|
||||
currency: z.string().nullish(),
|
||||
has_prorations: z.boolean().nullish(),
|
||||
next_cycle: z
|
||||
.object({
|
||||
starts_at: z.number(),
|
||||
total: z.number(),
|
||||
})
|
||||
.nullish(),
|
||||
});
|
||||
// export const CheckoutResponseSchema = z.object({
|
||||
// url: z.string().nullish(),
|
||||
// customer_id: z.string(),
|
||||
// lines: z.array(CheckoutLineSchema),
|
||||
// product: ApiProductSchema.nullish(),
|
||||
// current_product: ApiProductSchema.nullish(),
|
||||
// options: z.array(FeatureOptionsSchema).nullish(),
|
||||
// total: z.number().nullish(),
|
||||
// currency: z.string().nullish(),
|
||||
// has_prorations: z.boolean().nullish(),
|
||||
// next_cycle: z
|
||||
// .object({
|
||||
// starts_at: z.number(),
|
||||
// total: z.number(),
|
||||
// })
|
||||
// .nullish(),
|
||||
// });
|
||||
|
||||
export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
export type CheckoutParams = z.infer<typeof CheckoutParamsSchema>;
|
||||
// export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
// export type CheckoutParams = z.infer<typeof CheckoutParamsSchema>;
|
||||
|
||||
@@ -19,6 +19,14 @@ export const ApiSubscriptionSchema = z.object({
|
||||
current_period_start: z.number().nullable(),
|
||||
current_period_end: z.number().nullable(),
|
||||
quantity: z.number(),
|
||||
|
||||
// feature_quantities: z.array(
|
||||
// z.object({
|
||||
// feature_id: z.string(),
|
||||
// quantity: z.number(),
|
||||
// upcoming_quantity: z.number().nullable(),
|
||||
// }),
|
||||
// ),
|
||||
});
|
||||
|
||||
export type ApiSubscription = z.infer<typeof ApiSubscriptionSchema>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Core
|
||||
export * from "./core/attachModels.js";
|
||||
export * from "./core/checkoutModels.js";
|
||||
|
||||
// NOTE: coreOpenApi.js is NOT exported here - it's only imported by openapi.ts for spec generation
|
||||
export * from "./core/coreOpModels.js";
|
||||
|
||||
@@ -70,6 +70,10 @@ export * from "./balances/track/trackParams.js";
|
||||
export * from "./balances/track/trackResponseV2.js";
|
||||
export * from "./balances/track/trackTypes/pgDeductionUpdate.js";
|
||||
export * from "./balances/usageModels.js";
|
||||
export * from "./billing/checkout/prevVersions/checkoutParamsV0.js";
|
||||
export * from "./billing/checkout/prevVersions/checkoutParamsV0.js";
|
||||
export * from "./billing/checkout/prevVersions/checkoutResponseV0.js";
|
||||
|
||||
export * from "./common/customerData.js";
|
||||
export * from "./common/entityData.js";
|
||||
export * from "./customers/cusFeatures/cusFeatureLegacyData.js";
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { z } from "zod/v4";
|
||||
import {
|
||||
AttachBodySchema,
|
||||
ExtAttachBodySchema,
|
||||
} from "../../core/attachModels.js";
|
||||
|
||||
export const ExtCheckoutParamsSchema = ExtAttachBodySchema.extend({
|
||||
setup_payment: z.boolean().optional(),
|
||||
}).meta({
|
||||
description:
|
||||
"Returns a Stripe Checkout URL for the customer to make a payment, or returns payment confirmation information.",
|
||||
});
|
||||
|
||||
export const CheckoutParamsSchema = AttachBodySchema.extend({
|
||||
// skip_checkout: z.boolean().optional(),
|
||||
setup_payment: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type CheckoutParams = z.infer<typeof CheckoutParamsSchema>;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
|
||||
import { z } from "zod/v4";
|
||||
import { ApiPlanSchema } from "../../products/apiPlan.js";
|
||||
|
||||
// RESULT
|
||||
export const CheckoutLineSchema = z.object({
|
||||
description: z.string(),
|
||||
amount: z.number(),
|
||||
// item: ApiProductItemSchema.nullish(),
|
||||
});
|
||||
|
||||
export const CheckoutResponseSchema = z.object({
|
||||
url: z.string().nullish(),
|
||||
customer_id: z.string(),
|
||||
lines: z.array(CheckoutLineSchema),
|
||||
|
||||
plan: ApiPlanSchema.nullish(),
|
||||
current_plan: ApiPlanSchema.nullish(),
|
||||
|
||||
options: z.array(FeatureOptionsSchema).nullish(),
|
||||
total: z.number().nullish(),
|
||||
currency: z.string().nullish(),
|
||||
has_prorations: z.boolean().nullish(),
|
||||
next_cycle: z
|
||||
.object({
|
||||
starts_at: z.number(),
|
||||
total: z.number(),
|
||||
})
|
||||
.nullish(),
|
||||
});
|
||||
|
||||
export type CheckoutLine = z.infer<typeof CheckoutLineSchema>;
|
||||
@@ -15,6 +15,7 @@ export enum AffectedResource {
|
||||
Feature = "feature",
|
||||
Check = "check",
|
||||
Track = "track",
|
||||
Checkout = "checkout",
|
||||
// Add more as needed
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user