Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/stripe

src/customers.ts

193 lines · 5.2 KB · TypeScript
import {
	mutationPreview,
	parseAction,
	stripeDate,
	stripeList,
	stripeRequest,
	stripeSearch,
	type StripeAuthOptions,
	type StripeDryRun,
} from './stripe-core.ts'

export function summarizeCustomer(customer: any) {
	if (!customer || typeof customer !== 'object') return null
	return {
		id: customer.id ?? null,
		email: customer.email ?? null,
		name: customer.name ?? null,
		description: customer.description ?? null,
		created: stripeDate(customer.created),
		currency: customer.currency ?? null,
		delinquent: Boolean(customer.delinquent),
		metadata: customer.metadata ?? {},
	}
}

export type ListCustomersInput = StripeAuthOptions & {
	email?: string
	maxItems?: number
}

/** List customers, optionally filtered by exact email. */
export async function listCustomers(input: ListCustomersInput = {}) {
	const { items, hasMore } = await stripeList('customers', {
		account: input.account,
		secretName: input.secretName,
		maxItems: input.maxItems ?? 25,
		query: { email: input.email },
	})
	return { hasMore, customers: items.map(summarizeCustomer) }
}

/**
 * Search customers with Stripe's search query language.
 * @example searchCustomers({ searchQuery: "email:'ada@example.com'" })
 */
export async function searchCustomers(
	input: StripeAuthOptions & { searchQuery: string; maxItems?: number },
) {
	const { items, hasMore } = await stripeSearch('customers/search', {
		account: input.account,
		secretName: input.secretName,
		searchQuery: input.searchQuery,
		maxItems: input.maxItems ?? 25,
	})
	return { hasMore, customers: items.map(summarizeCustomer) }
}

/** Get one customer by id (full Stripe object). */
export async function getCustomer(input: StripeAuthOptions & { customerId: string }) {
	return await stripeRequest({
		path: 'customers/' + input.customerId,
		account: input.account,
		secretName: input.secretName,
	})
}

export type CreateCustomerInput = StripeAuthOptions & {
	email?: string
	name?: string
	description?: string
	phone?: string
	metadata?: Record<string, string>
	idempotencyKey?: string
	dryRun?: boolean
}

/** Create a customer. Pass `dryRun: true` to preview without calling Stripe. */
export async function createCustomer(
	input: CreateCustomerInput = {},
): Promise<StripeDryRun | ReturnType<typeof summarizeCustomer>> {
	const body = {
		email: input.email,
		name: input.name,
		description: input.description,
		phone: input.phone,
		metadata: input.metadata,
	}
	const preview = mutationPreview(input, {
		action: 'create customer',
		method: 'POST',
		path: 'customers',
		body,
	})
	if (preview) return preview
	const customer = await stripeRequest({
		path: 'customers',
		method: 'POST',
		account: input.account,
		secretName: input.secretName,
		idempotencyKey: input.idempotencyKey,
		body,
	})
	return summarizeCustomer(customer)
}

export type UpdateCustomerInput = StripeAuthOptions & {
	customerId: string
	email?: string
	name?: string
	description?: string
	phone?: string
	metadata?: Record<string, string>
	dryRun?: boolean
}

/** Update mutable customer fields. Pass `dryRun: true` to preview. */
export async function updateCustomer(input: UpdateCustomerInput) {
	const body = {
		email: input.email,
		name: input.name,
		description: input.description,
		phone: input.phone,
		metadata: input.metadata,
	}
	const preview = mutationPreview(input, {
		action: 'update customer ' + input.customerId,
		method: 'POST',
		path: 'customers/' + input.customerId,
		body,
	})
	if (preview) return preview
	const customer = await stripeRequest({
		path: 'customers/' + input.customerId,
		method: 'POST',
		account: input.account,
		secretName: input.secretName,
		body,
	})
	return summarizeCustomer(customer)
}

/** Permanently delete a customer. Requires confirm: true. Pass dryRun: true to preview. */
export async function deleteCustomer(
	input: StripeAuthOptions & { customerId: string; confirm?: boolean; dryRun?: boolean },
) {
	const preview = mutationPreview(input, {
		action: 'delete customer ' + input.customerId,
		method: 'DELETE',
		path: 'customers/' + input.customerId,
		requireConfirm: true,
	})
	if (preview) return preview
	return await stripeRequest({
		path: 'customers/' + input.customerId,
		method: 'DELETE',
		account: input.account,
		secretName: input.secretName,
	})
}

const customerActions = [
	'list-customers',
	'search-customers',
	'get-customer',
	'create-customer',
	'update-customer',
	'delete-customer',
] as const

/**
 * Customers dispatcher. Defaults to list-customers.
 */
export default async function customers(input: Record<string, unknown> = {}) {
	const action = parseAction(input.action, customerActions, 'list-customers', 'customers')
	switch (action) {
		case 'list-customers':
			return await listCustomers(input as ListCustomersInput)
		case 'search-customers':
			return await searchCustomers(input as never)
		case 'get-customer':
			return await getCustomer(input as never)
		case 'create-customer':
			return await createCustomer(input as CreateCustomerInput)
		case 'update-customer':
			return await updateCustomer(input as UpdateCustomerInput)
		case 'delete-customer':
			return await deleteCustomer(input as never)
		default: {
			const exhaustive: never = action
			throw new Error('Unhandled customers action: ' + String(exhaustive))
		}
	}
}