← Public packages
@kentcdodds/stripe
Stripe helpers for customers, payments, invoices, subscriptions, products, payment links, refunds, and balance.
src/products.ts
223 lines · 6.7 KB · TypeScriptimport {
formatStripeAmount,
idOf,
requireConfirm,
stripeDate,
stripeList,
stripeRequest,
} from './stripe-core.ts'
export function summarizeProduct(product: any) {
if (!product || typeof product !== 'object') return null
return {
id: product.id ?? null,
name: product.name ?? null,
active: Boolean(product.active),
description: product.description ?? null,
defaultPriceId: idOf(product.default_price),
created: stripeDate(product.created),
metadata: product.metadata ?? {},
}
}
export function summarizePrice(price: any) {
if (!price || typeof price !== 'object') return null
return {
id: price.id ?? null,
productId: idOf(price.product),
active: Boolean(price.active),
nickname: price.nickname ?? null,
unitAmount: price.unit_amount ?? null,
currency: price.currency ?? null,
display: formatStripeAmount(price.unit_amount, price.currency),
type: price.type ?? null,
interval: price.recurring?.interval ?? null,
intervalCount: price.recurring?.interval_count ?? null,
created: stripeDate(price.created),
}
}
/** List products (active only by default). */
export async function listProducts(input: { active?: boolean; maxItems?: number } = {}) {
const { items, hasMore } = await stripeList('products', {
maxItems: input.maxItems ?? 25,
query: { active: input.active ?? true },
})
return { hasMore, products: items.map(summarizeProduct) }
}
/** Get one product by id (full Stripe object). */
export async function getProduct(input: { productId: string }) {
return await stripeRequest({ path: 'products/' + input.productId })
}
/** List prices, optionally for one product. */
export async function listPrices(
input: { productId?: string; active?: boolean; maxItems?: number } = {},
) {
const { items, hasMore } = await stripeList('prices', {
maxItems: input.maxItems ?? 25,
query: { product: input.productId, active: input.active ?? true },
})
return { hasMore, prices: items.map(summarizePrice) }
}
export type CreateProductInput = {
name: string
description?: string
metadata?: Record<string, string>
/** Create a default price alongside the product. */
price?: {
/** Amount in smallest currency unit, e.g. cents. */
amount: number
currency?: string
/** Omit for a one-time price. */
interval?: 'day' | 'week' | 'month' | 'year'
}
idempotencyKey?: string
}
/** Create a product, optionally with a default price. */
export async function createProduct(input: CreateProductInput) {
const product = await stripeRequest({
path: 'products',
method: 'POST',
idempotencyKey: input.idempotencyKey,
body: {
name: input.name,
description: input.description,
metadata: input.metadata,
default_price_data: input.price
? {
unit_amount: input.price.amount,
currency: input.price.currency ?? 'usd',
recurring: input.price.interval ? { interval: input.price.interval } : undefined,
}
: undefined,
},
})
return summarizeProduct(product)
}
export type CreatePriceInput = {
productId: string
/** Amount in smallest currency unit, e.g. cents. */
amount: number
currency?: string
/** Omit for a one-time price. */
interval?: 'day' | 'week' | 'month' | 'year'
nickname?: string
idempotencyKey?: string
}
/** Create a price for an existing product. */
export async function createPrice(input: CreatePriceInput) {
const price = await stripeRequest({
path: 'prices',
method: 'POST',
idempotencyKey: input.idempotencyKey,
body: {
product: input.productId,
unit_amount: input.amount,
currency: input.currency ?? 'usd',
recurring: input.interval ? { interval: input.interval } : undefined,
nickname: input.nickname,
},
})
return summarizePrice(price)
}
/** Archive (deactivate) a product. Requires confirm: true. */
export async function archiveProduct(input: { productId: string; confirm?: boolean }) {
requireConfirm(input, 'archive product ' + input.productId)
const product = await stripeRequest({
path: 'products/' + input.productId,
method: 'POST',
body: { active: false },
})
return summarizeProduct(product)
}
export function summarizePaymentLink(link: any) {
if (!link || typeof link !== 'object') return null
return {
id: link.id ?? null,
url: link.url ?? null,
active: Boolean(link.active),
currency: link.currency ?? null,
metadata: link.metadata ?? {},
}
}
/** List payment links. */
export async function listPaymentLinks(input: { active?: boolean; maxItems?: number } = {}) {
const { items, hasMore } = await stripeList('payment_links', {
maxItems: input.maxItems ?? 25,
query: { active: input.active },
})
return { hasMore, paymentLinks: items.map(summarizePaymentLink) }
}
export type CreatePaymentLinkInput = {
priceId: string
quantity?: number
metadata?: Record<string, string>
idempotencyKey?: string
}
/** Create a shareable payment link for an existing price. */
export async function createPaymentLink(input: CreatePaymentLinkInput) {
const link = await stripeRequest({
path: 'payment_links',
method: 'POST',
idempotencyKey: input.idempotencyKey,
body: {
line_items: [{ price: input.priceId, quantity: input.quantity ?? 1 }],
metadata: input.metadata,
},
})
return summarizePaymentLink(link)
}
/** Deactivate a payment link (links cannot be deleted). Requires confirm: true. */
export async function deactivatePaymentLink(input: { paymentLinkId: string; confirm?: boolean }) {
requireConfirm(input, 'deactivate payment link ' + input.paymentLinkId)
const link = await stripeRequest({
path: 'payment_links/' + input.paymentLinkId,
method: 'POST',
body: { active: false },
})
return summarizePaymentLink(link)
}
/**
* Products/prices/payment-links dispatcher. Defaults to list-products.
* Actions: list-products, get-product, create-product, archive-product,
* list-prices, create-price, list-payment-links, create-payment-link,
* deactivate-payment-link.
*/
export default async function products(input: Record<string, unknown> = {}) {
const action = String(input.action ?? 'list-products')
switch (action) {
case 'list-products':
return await listProducts(input as never)
case 'get-product':
return await getProduct(input as never)
case 'create-product':
return await createProduct(input as CreateProductInput)
case 'archive-product':
return await archiveProduct(input as never)
case 'list-prices':
return await listPrices(input as never)
case 'create-price':
return await createPrice(input as CreatePriceInput)
case 'list-payment-links':
return await listPaymentLinks(input as never)
case 'create-payment-link':
return await createPaymentLink(input as CreatePaymentLinkInput)
case 'deactivate-payment-link':
return await deactivatePaymentLink(input as never)
default:
throw new Error('Unknown products action: ' + action)
}
}