feat: 🎸 openapi contracts for schedules and get customer

This commit is contained in:
amianthus
2026-05-06 12:00:13 +01:00
parent b556c7d68e
commit 33ac5a942d
8 changed files with 5354 additions and 1756 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@ import {
AttachParamsV1Schema,
BILLING_PREVIEW_RESPONSE_EXAMPLE,
BillingResponseSchema,
CreateScheduleParamsV0Schema,
CreateScheduleResponseSchema,
ExtAttachPreviewResponseSchema,
ExtPreviewUpdateSubscriptionResponseSchema,
ExtUpdateSubscriptionV1ParamsSchema,
@@ -14,6 +16,7 @@ import {
import { oc } from "@orpc/contract";
import {
billingAttachJsDoc,
billingCreateScheduleJsDoc,
billingMultiAttachJsDoc,
billingPreviewAttachJsDoc,
billingPreviewMultiAttachJsDoc,
@@ -227,6 +230,66 @@ export const billingSetupPaymentContract = oc
}),
);
export const billingCreateScheduleContract = oc
.route({
method: "POST",
path: "/v1/billing.create_schedule",
operationId: "createSchedule",
tags: ["billing"],
description: billingCreateScheduleJsDoc,
spec: (spec) => ({
...spec,
"x-speakeasy-name-override": "createSchedule",
}),
})
.input(
CreateScheduleParamsV0Schema.meta({
title: "CreateScheduleParams",
examples: [
{
customer_id: "cus_123",
phases: [
{
starts_at: 1735689600000,
plans: [{ plan_id: "trial_plan" }],
},
{
starts_at: 1736899200000,
plans: [{ plan_id: "pro_plan" }],
},
],
},
],
}),
)
.output(
CreateScheduleResponseSchema.meta({
title: "CreateScheduleResponse",
examples: [
{
customer_id: "cus_123",
entity_id: null,
status: "created",
schedule_id: "sch_1234",
phases: [
{
phase_id: "sphs_1111",
starts_at: 1735689600000,
customer_product_ids: ["cus_prod_1111"],
},
{
phase_id: "sphs_2222",
starts_at: 1736899200000,
customer_product_ids: ["cus_prod_2222"],
},
],
invoice: null,
payment_url: null,
},
],
}),
);
export const billingMultiAttachContract = oc
.route({
method: "POST",

View File

@@ -9,10 +9,14 @@ import {
DeleteCustomerParamsSchema,
DeleteCustomerResponseSchema,
} from "@api/customers/crud/deleteCustomerParams.js";
import { GetCustomerParamsV1Schema } from "@api/customers/crud/getCustomerParams.js";
import { ListCustomersV2ParamsSchema } from "@api/customers/crud/listCustomersParamsV2.js";
import { UpdateCustomerParamsV1Schema } from "@api/customers/crud/updateCustomerParams.js";
import { oc } from "@orpc/contract";
import { getOrCreateCustomerJsDoc } from "../jsDocs/customerJsDocs";
import {
getCustomerJsDoc,
getOrCreateCustomerJsDoc,
} from "../jsDocs/customerJsDocs";
export const getOrCreateCustomerContract = oc
.route({
@@ -40,6 +44,38 @@ export const getOrCreateCustomerContract = oc
)
.output(ApiCustomerV5Schema);
export const getCustomerContract = oc
.route({
method: "POST",
path: "/v1/customers.get",
operationId: "getCustomer",
tags: ["customers"],
description: getCustomerJsDoc,
spec: (spec) => ({
...spec,
"x-speakeasy-name-override": "get",
}),
})
.input(
GetCustomerParamsV1Schema.meta({
title: "GetCustomerParams",
examples: [
{
customer_id: "cus_123",
},
{
customer_id: "cus_123",
expand: ["invoices", "entities"],
},
],
}),
)
.output(
ApiCustomerV5Schema.meta({
examples: [API_CUSTOMER_V5_EXAMPLE],
}),
);
export const listCustomersContract = oc
.route({
method: "POST",

View File

@@ -9,6 +9,7 @@ import {
} from "./balancesContract.js";
import {
billingAttachContract,
billingCreateScheduleContract,
billingMultiAttachContract,
billingOpenCustomerPortalContract,
billingPreviewAttachContract,
@@ -19,6 +20,7 @@ import {
} from "./billingContract.js";
import {
deleteCustomerContract,
getCustomerContract,
getOrCreateCustomerContract,
listCustomersContract,
updateCustomerContract,
@@ -55,6 +57,7 @@ import {
export const v2_1ContractRouter = oc.router({
// Customers
getOrCreateCustomer: getOrCreateCustomerContract,
getCustomer: getCustomerContract,
listCustomers: listCustomersContract,
updateCustomer: updateCustomerContract,
deleteCustomer: deleteCustomerContract,
@@ -75,6 +78,7 @@ export const v2_1ContractRouter = oc.router({
// Billing
billingAttach: billingAttachContract,
billingCreateSchedule: billingCreateScheduleContract,
billingMultiAttach: billingMultiAttachContract,
billingPreviewAttach: billingPreviewAttachContract,
billingPreviewMultiAttach: billingPreviewMultiAttachContract,

View File

@@ -1,5 +1,6 @@
import {
AttachParamsV1Schema,
CreateScheduleParamsV0Schema,
MultiAttachParamsV0Schema,
UpdateSubscriptionV1ParamsSchema,
} from "@autumn/shared";
@@ -179,6 +180,35 @@ export const billingMultiAttachJsDoc = createJSDocDescription({
"A billing response with customer ID, invoice details, and payment URL (if checkout required).",
});
export const billingCreateScheduleJsDoc = createJSDocDescription({
description:
"Creates a multi-phase subscription schedule for a customer. The first phase starts immediately and subsequent phases automatically transition at their scheduled start times.",
whenToUse:
"Use this endpoint to schedule future plan changes (e.g. switch from a trial plan to a paid plan on a specific date) or to define a sequence of plans that should activate over time.",
body: CreateScheduleParamsV0Schema,
examples: [
example({
description: "Schedule a transition from a trial plan to a paid plan",
values: {
customerId: "cus_123",
phases: [
{
startsAt: Date.now(),
plans: [{ planId: "trial_plan" }],
},
{
startsAt: Date.now() + 14 * 24 * 60 * 60 * 1000,
plans: [{ planId: "pro_plan" }],
},
],
},
}),
],
methodName: "billing.createSchedule",
returns:
"A create-schedule response with the schedule ID, persisted phases, and any required payment or checkout URL.",
});
export const billingPreviewMultiAttachJsDoc = createJSDocDescription({
description:
"Previews the billing changes that would occur when attaching multiple plans, without actually making any changes.",

View File

@@ -1,4 +1,7 @@
import { CreateCustomerParamsV0Schema } from "@autumn/shared";
import {
CreateCustomerParamsV0Schema,
GetCustomerParamsV1Schema,
} from "@autumn/shared";
import { createJSDocDescription, example } from "../../utils/jsDocs/index.js";
export const getOrCreateCustomerJsDoc = createJSDocDescription({
@@ -19,3 +22,27 @@ export const getOrCreateCustomerJsDoc = createJSDocDescription({
],
methodName: "getOrCreate",
});
export const getCustomerJsDoc = createJSDocDescription({
description:
"Fetches a customer by ID, optionally expanding related data such as invoices or entities.",
whenToUse:
"Use this when you know the customer exists or assert they exist without creating them.",
body: GetCustomerParamsV1Schema,
examples: [
example({
description: "Fetch a customer by external ID",
values: {
customerId: "cus_123",
},
}),
example({
description: "Fetch a customer with expanded invoices and entities",
values: {
customerId: "cus_123",
expand: ["invoices", "entities"],
},
}),
],
methodName: "get",
});

View File

@@ -16,6 +16,7 @@ import {
CustomerDataSchema,
CustomerExpandEnum,
CustomerIdSchema,
GetCustomerParamsV1Schema,
LATEST_VERSION,
PreviewUpdateSubscriptionResponseSchema,
SetupPaymentParamsV1Schema,
@@ -54,6 +55,7 @@ async function generateOpenApiDocument(): Promise<Record<string, unknown>> {
// in the OpenAPI output, which removeInternalFields() will then strip
registerInternalSchemas(BaseApiCustomerSchema);
registerInternalSchemas(CreateCustomerParamsV1Schema);
registerInternalSchemas(GetCustomerParamsV1Schema);
registerInternalSchemas(AttachParamsV1Schema);
registerInternalSchemas(UpdateSubscriptionV1ParamsSchema);
registerInternalSchemas(SetupPaymentParamsV1Schema);