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, AttachParamsV1Schema,
BILLING_PREVIEW_RESPONSE_EXAMPLE, BILLING_PREVIEW_RESPONSE_EXAMPLE,
BillingResponseSchema, BillingResponseSchema,
CreateScheduleParamsV0Schema,
CreateScheduleResponseSchema,
ExtAttachPreviewResponseSchema, ExtAttachPreviewResponseSchema,
ExtPreviewUpdateSubscriptionResponseSchema, ExtPreviewUpdateSubscriptionResponseSchema,
ExtUpdateSubscriptionV1ParamsSchema, ExtUpdateSubscriptionV1ParamsSchema,
@@ -14,6 +16,7 @@ import {
import { oc } from "@orpc/contract"; import { oc } from "@orpc/contract";
import { import {
billingAttachJsDoc, billingAttachJsDoc,
billingCreateScheduleJsDoc,
billingMultiAttachJsDoc, billingMultiAttachJsDoc,
billingPreviewAttachJsDoc, billingPreviewAttachJsDoc,
billingPreviewMultiAttachJsDoc, 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 export const billingMultiAttachContract = oc
.route({ .route({
method: "POST", method: "POST",

View File

@@ -9,10 +9,14 @@ import {
DeleteCustomerParamsSchema, DeleteCustomerParamsSchema,
DeleteCustomerResponseSchema, DeleteCustomerResponseSchema,
} from "@api/customers/crud/deleteCustomerParams.js"; } from "@api/customers/crud/deleteCustomerParams.js";
import { GetCustomerParamsV1Schema } from "@api/customers/crud/getCustomerParams.js";
import { ListCustomersV2ParamsSchema } from "@api/customers/crud/listCustomersParamsV2.js"; import { ListCustomersV2ParamsSchema } from "@api/customers/crud/listCustomersParamsV2.js";
import { UpdateCustomerParamsV1Schema } from "@api/customers/crud/updateCustomerParams.js"; import { UpdateCustomerParamsV1Schema } from "@api/customers/crud/updateCustomerParams.js";
import { oc } from "@orpc/contract"; import { oc } from "@orpc/contract";
import { getOrCreateCustomerJsDoc } from "../jsDocs/customerJsDocs"; import {
getCustomerJsDoc,
getOrCreateCustomerJsDoc,
} from "../jsDocs/customerJsDocs";
export const getOrCreateCustomerContract = oc export const getOrCreateCustomerContract = oc
.route({ .route({
@@ -40,6 +44,38 @@ export const getOrCreateCustomerContract = oc
) )
.output(ApiCustomerV5Schema); .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 export const listCustomersContract = oc
.route({ .route({
method: "POST", method: "POST",

View File

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

View File

@@ -1,5 +1,6 @@
import { import {
AttachParamsV1Schema, AttachParamsV1Schema,
CreateScheduleParamsV0Schema,
MultiAttachParamsV0Schema, MultiAttachParamsV0Schema,
UpdateSubscriptionV1ParamsSchema, UpdateSubscriptionV1ParamsSchema,
} from "@autumn/shared"; } 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).", "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({ export const billingPreviewMultiAttachJsDoc = createJSDocDescription({
description: description:
"Previews the billing changes that would occur when attaching multiple plans, without actually making any changes.", "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"; import { createJSDocDescription, example } from "../../utils/jsDocs/index.js";
export const getOrCreateCustomerJsDoc = createJSDocDescription({ export const getOrCreateCustomerJsDoc = createJSDocDescription({
@@ -19,3 +22,27 @@ export const getOrCreateCustomerJsDoc = createJSDocDescription({
], ],
methodName: "getOrCreate", 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, CustomerDataSchema,
CustomerExpandEnum, CustomerExpandEnum,
CustomerIdSchema, CustomerIdSchema,
GetCustomerParamsV1Schema,
LATEST_VERSION, LATEST_VERSION,
PreviewUpdateSubscriptionResponseSchema, PreviewUpdateSubscriptionResponseSchema,
SetupPaymentParamsV1Schema, SetupPaymentParamsV1Schema,
@@ -54,6 +55,7 @@ async function generateOpenApiDocument(): Promise<Record<string, unknown>> {
// in the OpenAPI output, which removeInternalFields() will then strip // in the OpenAPI output, which removeInternalFields() will then strip
registerInternalSchemas(BaseApiCustomerSchema); registerInternalSchemas(BaseApiCustomerSchema);
registerInternalSchemas(CreateCustomerParamsV1Schema); registerInternalSchemas(CreateCustomerParamsV1Schema);
registerInternalSchemas(GetCustomerParamsV1Schema);
registerInternalSchemas(AttachParamsV1Schema); registerInternalSchemas(AttachParamsV1Schema);
registerInternalSchemas(UpdateSubscriptionV1ParamsSchema); registerInternalSchemas(UpdateSubscriptionV1ParamsSchema);
registerInternalSchemas(SetupPaymentParamsV1Schema); registerInternalSchemas(SetupPaymentParamsV1Schema);