Skip to content
← Public packages

@kentcdodds/stripe

Stripe helpers for customers, payments, invoices, subscriptions, products, payment links, refunds, and balance.

src/customers.ts

134 lines · 3.9 KB · TypeScript
import {
	requireConfirm,
	stripeDate,
	stripeList,
	stripeRequest,
	stripeSearch,
} 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 = {
	email?: string
	maxItems?: number
}

/** List customers, optionally filtered by exact email. */
export async function listCustomers(input: ListCustomersInput = {}) {
	const { items, hasMore } = await stripeList('customers', {
		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'" })
 * @example searchCustomers({ searchQuery: "name~'Ada' AND created>1735689600" })
 */
export async function searchCustomers(input: { searchQuery: string; maxItems?: number }) {
	const { items, hasMore } = await stripeSearch('customers/search', {
		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: { customerId: string }) {
	return await stripeRequest({ path: 'customers/' + input.customerId })
}

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

/** Create a customer. */
export async function createCustomer(input: CreateCustomerInput = {}) {
	const customer = await stripeRequest({
		path: 'customers',
		method: 'POST',
		idempotencyKey: input.idempotencyKey,
		body: {
			email: input.email,
			name: input.name,
			description: input.description,
			phone: input.phone,
			metadata: input.metadata,
		},
	})
	return summarizeCustomer(customer)
}

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

/** Update mutable customer fields. */
export async function updateCustomer(input: UpdateCustomerInput) {
	const customer = await stripeRequest({
		path: 'customers/' + input.customerId,
		method: 'POST',
		body: {
			email: input.email,
			name: input.name,
			description: input.description,
			phone: input.phone,
			metadata: input.metadata,
		},
	})
	return summarizeCustomer(customer)
}

/** Permanently delete a customer. Requires confirm: true. */
export async function deleteCustomer(input: { customerId: string; confirm?: boolean }) {
	requireConfirm(input, 'delete customer ' + input.customerId)
	return await stripeRequest({ path: 'customers/' + input.customerId, method: 'DELETE' })
}

/**
 * Customers dispatcher. Defaults to list-customers.
 * Actions: list-customers, search-customers, get-customer, create-customer, update-customer, delete-customer.
 */
export default async function customers(input: Record<string, unknown> = {}) {
	const action = String(input.action ?? 'list-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:
			throw new Error('Unknown customers action: ' + action)
	}
}