chore: added setup payment v1 params

This commit is contained in:
John Yeo
2026-02-23 11:21:20 +00:00
parent f08fe4e9f7
commit b6de9d5ac0
10 changed files with 125 additions and 42 deletions

View File

@@ -3,6 +3,7 @@ import { CronJob } from "cron";
import { initDrizzle } from "../db/initDrizzle.js";
import { logger } from "../external/logtail/logtailUtils.js";
import { runInvoiceCron } from "./invoiceCron/runInvoiceCron.js";
import { runOneOffCleanup } from "./oneoffCron/runOneOffCleanup.js";
import { runProductCron } from "./productCron/runProductCron.js";
import { runResetCron } from "./resetCron/runResetCron.js";
import type { CronContext } from "./utils/CronContext.js";
@@ -23,6 +24,7 @@ const main = async () => {
runResetCron({ ctx }),
runProductCron(),
runInvoiceCron({ ctx }),
runOneOffCleanup({ ctx }),
]);
};

View File

@@ -0,0 +1,49 @@
import { CusProductStatus } from "@autumn/shared";
import { getOneOffCustomerProductsToCleanup } from "@/internal/customers/cusProducts/actions/cleanupOneOff/getOneOffToCleanup.js";
import { batchUpdateCustomerProducts } from "@/internal/customers/cusProducts/repos/batchUpdateCustomerProducts.js";
import type { CronContext } from "../utils/CronContext.js";
/** Dry-run: fetches one-off customer products eligible for cleanup and logs them without making any updates. */
export const runOneOffCleanup = async ({ ctx }: { ctx: CronContext }) => {
const { logger } = ctx;
try {
const toCleanup = await getOneOffCustomerProductsToCleanup({
ctx,
});
console.log(`Found ${toCleanup.length} customer products to cleanup`);
logger.info(`Found ${toCleanup.length} customer products to cleanup`);
// Log the results
for (const result of toCleanup) {
logger.info(
`[One-off Cleanup] expiring customer product: ${result.customer_product.id} (${result.org.slug})`,
{
context: {
org_id: result.org.id,
org_slug: result.org.slug,
customer_id: result.customer.id,
env: result.customer.env,
},
data: {
customerProductId: result.customer_product.id,
productId: result.product.id,
},
},
);
}
await batchUpdateCustomerProducts({
db: ctx.db,
updates: toCleanup.map((result) => ({
id: result.customer_product.id,
updates: { status: CusProductStatus.Expired },
})),
});
logger.info(`Expired ${toCleanup.length} customer products`);
console.log(`Expired ${toCleanup.length} customer products`);
} catch (error) {
console.error("[One-off Cleanup DRY RUN] Error:", error);
}
};

View File

@@ -180,6 +180,15 @@ export class CusProductService {
return cusProducts as FullCusProduct[];
}
static async getFull({ db, id }: { db: DrizzleCli; id: string }) {
const data = await db.query.customerProducts.findFirst({
where: eq(customerProducts.id, id),
with: getFullCusProdRelations(),
});
return data as FullCusProduct | undefined;
}
static async getByInternalProductId({
db,
internalProductId,

View File

@@ -11,7 +11,7 @@ import {
type Price,
type Product,
} from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import type { CronContext } from "@/cron/utils/CronContext";
export type OneOffCleanupResult = {
customer_product: CustomerProduct;
@@ -36,7 +36,7 @@ export type OneOffCleanupResult = {
export const getOneOffCustomerProductsToCleanup = async ({
ctx,
}: {
ctx: AutumnContext;
ctx: CronContext;
}): Promise<OneOffCleanupResult[]> => {
const result = await ctx.db.execute<{
customer_product: CustomerProduct;

View File

@@ -1,15 +0,0 @@
import type { InsertCustomerProduct } from "@shared/models/cusProductModels/cusProductTable";
import type { AutumnContext } from "@/honoUtils/HonoEnv";
export const batchUpdateCustomerProducts = async ({
ctx,
updates,
}: {
ctx: AutumnContext;
updates: {
id: string;
updates: Partial<InsertCustomerProduct>;
}[];
}) => {
const { db } = ctx;
};

View File

@@ -28,6 +28,11 @@ export const AttachParamsV1Schema = BillingParamsBaseV1Schema.extend({
description:
"When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are scheduled.",
}),
checkout_session_params: z.record(z.string(), z.unknown()).optional().meta({
description:
"Additional parameters to pass into the creation of the Stripe checkout session.",
}),
});
export type AttachParamsV1 = z.infer<typeof AttachParamsV1Schema>;

View File

@@ -15,6 +15,10 @@ export * from "./checkout/prevVersions/checkoutResponseV0";
export * from "./common/index";
export * from "./openBillingPortal/openBillingPortalParamsV1";
export * from "./openBillingPortal/openBillingPortalResponse";
// Setup Payment
export * from "./setupPayment/setupPaymentParamsV0";
export * from "./setupPayment/setupPaymentParamsV1";
// Update Subscription
export * from "./updateSubscription/previewUpdateSubscriptionResponse";
export * from "./updateSubscription/updateSubscriptionV0Params";

View File

@@ -0,0 +1,27 @@
import { z } from "zod/v4";
import { CustomerDataSchema } from "../../common/customerData";
export const SetupPaymentParamsSchema = z.object({
customer_id: z.string().meta({
description: "The ID of the customer",
}),
success_url: z.string().optional().meta({
description:
"URL to redirect to after successful payment setup. Must start with either http:// or https://",
}),
checkout_session_params: z.record(z.string(), z.unknown()).optional().meta({
description: "Additional parameters for the checkout session",
}),
customer_data: CustomerDataSchema.optional(),
});
export const SetupPaymentResponseV0Schema = z.object({
customer_id: z.string().meta({
description: "The ID of the customer",
}),
url: z.string().meta({
description: "URL to the payment setup page",
}),
});

View File

@@ -0,0 +1,26 @@
import { AttachParamsV1Schema } from "@api/models";
import { z } from "zod/v4";
export const SetupPaymentParamsV1Schema = AttachParamsV1Schema.omit({
invoice_mode: true,
redirect_mode: true,
new_billing_subscription: true,
plan_schedule: true,
});
export const SetupPaymentResponseV1Schema = z.object({
customer_id: z.string().meta({
description: "The ID of the customer",
}),
entity_id: z.string().optional().meta({
description:
"The ID of the entity the plan (if specified) will be attached to after setup.",
}),
url: z.string().meta({
description: "URL to redirect the customer to setup their payment.",
}),
});
export type SetupPaymentParamsV1 = z.infer<typeof SetupPaymentParamsV1Schema>;

View File

@@ -1,5 +1,4 @@
import { z } from "zod/v4";
import { CustomerDataSchema } from "../common/customerData.js";
// Cancel Schemas
export const CancelBodySchema = z.object({
@@ -68,29 +67,6 @@ export const QueryResultSchema = z.object({
}),
});
export const SetupPaymentParamsSchema = z.object({
customer_id: z.string().meta({
description: "The ID of the customer",
}),
success_url: z.string().optional().meta({
description:
"URL to redirect to after successful payment setup. Must start with either http:// or https://",
}),
customer_data: CustomerDataSchema.optional(),
checkout_session_params: z.record(z.string(), z.unknown()).optional().meta({
description: "Additional parameters for the checkout session",
}),
});
export const SetupPaymentResultSchema = z.object({
customer_id: z.string().meta({
description: "The ID of the customer",
}),
url: z.string().meta({
description: "URL to the payment setup page",
}),
});
export const BillingPortalParamsSchema = z.object({
return_url: z.string().optional().meta({
description:
@@ -111,6 +87,6 @@ export type CancelBody = z.infer<typeof CancelBodySchema>;
export type CancelResult = z.infer<typeof CancelResultSchema>;
export type QueryParams = z.infer<typeof QueryParamsSchema>;
export type QueryResult = z.infer<typeof QueryResultSchema>;
export type SetupPaymentParams = z.infer<typeof SetupPaymentParamsSchema>;
export type BillingPortalParams = z.infer<typeof BillingPortalParamsSchema>;
export type BillingPortalResult = z.infer<typeof BillingPortalResultSchema>;