diff --git a/src/contracts/subscription.ts b/src/contracts/subscription.ts new file mode 100644 index 0000000..a0af9e2 --- /dev/null +++ b/src/contracts/subscription.ts @@ -0,0 +1,49 @@ +import { oc } from "@orpc/contract"; +import { z } from "zod"; +import { SubscriptionSchema } from "../schemas/subscription"; + +export const CreateRenewalCheckoutInputSchema = z.object({ + subscriptionId: z.string(), +}); + +export const CreateRenewalCheckoutOutputSchema = z.object({ + checkoutId: z.string(), +}); + +export const CancelSubscriptionInputSchema = z.object({ + subscriptionId: z.string(), +}); + +export const CancelSubscriptionOutputSchema = z.object({ + ok: z.boolean(), +}); + +export const GetSubscriptionInputSchema = z.object({ + subscriptionId: z.string(), +}); + +export type CreateRenewalCheckout = z.infer< + typeof CreateRenewalCheckoutInputSchema +>; +export type CancelSubscriptionInput = z.infer< + typeof CancelSubscriptionInputSchema +>; +export type GetSubscriptionInput = z.infer; + +export const createRenewalCheckoutContract = oc + .input(CreateRenewalCheckoutInputSchema) + .output(CreateRenewalCheckoutOutputSchema); + +export const cancelSubscriptionContract = oc + .input(CancelSubscriptionInputSchema) + .output(CancelSubscriptionOutputSchema); + +export const getSubscriptionContract = oc + .input(GetSubscriptionInputSchema) + .output(SubscriptionSchema); + +export const subscription = { + createRenewalCheckout: createRenewalCheckoutContract, + cancel: cancelSubscriptionContract, + get: getSubscriptionContract, +}; diff --git a/src/index.ts b/src/index.ts index affa6d5..1705a65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { checkout } from "./contracts/checkout"; import { onboarding } from "./contracts/onboarding"; import { products } from "./contracts/products"; +import { subscription } from "./contracts/subscription"; export type { ConfirmCheckout, @@ -17,6 +18,11 @@ export type { StartDeviceAuth as StartDeviceAuthInput, StartDeviceAuthResponse, } from "./contracts/onboarding"; +export type { + CancelSubscriptionInput, + CreateRenewalCheckout, + GetSubscriptionInput, +} from "./contracts/subscription"; export type { Checkout } from "./schemas/checkout"; export { CheckoutSchema } from "./schemas/checkout"; export type { Currency } from "./schemas/currency"; @@ -27,8 +33,22 @@ export { ProductPriceSchema, ListProductsOutputSchema, } from "./contracts/products"; +export type { + RecurringInterval, + Subscription, + SubscriptionStatus, + SubscriptionWebhookEvent, + SubscriptionWebhookPayload, +} from "./schemas/subscription"; +export { + RecurringIntervalSchema, + SubscriptionSchema, + SubscriptionStatusSchema, + SubscriptionWebhookEventSchema, + SubscriptionWebhookPayloadSchema, +} from "./schemas/subscription"; -export const contract = { checkout, onboarding, products }; +export const contract = { checkout, onboarding, products, subscription }; export type { MetadataValidationError } from "./validation/metadata-validation"; export { diff --git a/src/schemas/subscription.ts b/src/schemas/subscription.ts new file mode 100644 index 0000000..ddb19da --- /dev/null +++ b/src/schemas/subscription.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; +import { CurrencySchema } from "./currency"; + +export const SubscriptionStatusSchema = z.enum([ + "active", + "past_due", + "canceled", +]); + +export const RecurringIntervalSchema = z.enum(["MONTH", "QUARTER", "YEAR"]); + +export const SubscriptionSchema = z.object({ + id: z.string(), + customerId: z.string(), + customerEmail: z.string(), + productId: z.string(), + amount: z.number(), + currency: CurrencySchema, + recurringInterval: RecurringIntervalSchema, + status: SubscriptionStatusSchema, + currentPeriodStart: z.string(), // ISO date + currentPeriodEnd: z.string(), // ISO date + cancelAtPeriodEnd: z.boolean().optional(), + endsAt: z.string().optional(), // ISO date (if scheduled to end) + endedAt: z.string().optional(), // ISO date (if ended) + canceledAt: z.string().optional(), // ISO date (if canceled) + startedAt: z.string(), // ISO date +}); + +export const SubscriptionWebhookEventSchema = z.enum([ + "subscription.created", + "subscription.renewed", + "subscription.canceled", + "subscription.payment_failed", +]); + +export const SubscriptionWebhookPayloadSchema = z.object({ + handler: z.literal("webhooks"), + event: SubscriptionWebhookEventSchema, + subscription: SubscriptionSchema, +}); + +export type Subscription = z.infer; +export type SubscriptionStatus = z.infer; +export type RecurringInterval = z.infer; +export type SubscriptionWebhookEvent = z.infer< + typeof SubscriptionWebhookEventSchema +>; +export type SubscriptionWebhookPayload = z.infer< + typeof SubscriptionWebhookPayloadSchema +>;