import {
formatStripeAmount,
idOf,
mutationPreview,
parseAction,
stripeDate,
stripeList,
stripeRequest,
stripeSearch,
type StripeAuthOptions,
} from './stripe-core.ts'
export function summarizePaymentIntent(intent: any) {
if (!intent || typeof intent !== 'object') return null
return {
id: intent.id ?? null,
status: intent.status ?? null,
amount: intent.amount ?? null,
currency: intent.currency ?? null,
display: formatStripeAmount(intent.amount, intent.currency),
customerId: idOf(intent.customer),
description: intent.description ?? null,
created: stripeDate(intent.created),
latestChargeId: idOf(intent.latest_charge),
receiptEmail: intent.receipt_email ?? null,
metadata: intent.metadata ?? {},
}
}
export function summarizeCharge(charge: any) {
if (!charge || typeof charge !== 'object') return null
return {
id: charge.id ?? null,
status: charge.status ?? null,
paid: Boolean(charge.paid),
refunded: Boolean(charge.refunded),
amount: charge.amount ?? null,
amountRefunded: charge.amount_refunded ?? null,
currency: charge.currency ?? null,
display: formatStripeAmount(charge.amount, charge.currency),
customerId: idOf(charge.customer),
paymentIntentId: idOf(charge.payment_intent),
description: charge.description ?? null,
receiptEmail: charge.receipt_email ?? charge.billing_details?.email ?? null,
billingName: charge.billing_details?.name ?? null,
created: stripeDate(charge.created),
receiptUrl: charge.receipt_url ?? null,
}
}
export type ListPaymentIntentsInput = StripeAuthOptions & {
customerId?: string
maxItems?: number
}
/** List payment intents, optionally for one customer. */
export async function listPaymentIntents(input: ListPaymentIntentsInput = {}) {
const { items, hasMore } = await stripeList('payment_intents', {
account: input.account,
secretName: input.secretName,
maxItems: input.maxItems ?? 25,
query: { customer: input.customerId },
})
return { hasMore, paymentIntents: items.map(summarizePaymentIntent) }
}
/** Get one payment intent by id (full Stripe object). */
export async function getPaymentIntent(input: StripeAuthOptions & { paymentIntentId: string }) {
return await stripeRequest({
path: 'payment_intents/' + input.paymentIntentId,
account: input.account,
secretName: input.secretName,
})
}
/**
* Search payment intents with Stripe's search query language.
* @example searchPaymentIntents({ searchQuery: "status:'succeeded' AND amount>1000" })
*/
export async function searchPaymentIntents(
input: StripeAuthOptions & { searchQuery: string; maxItems?: number },
) {
const { items, hasMore } = await stripeSearch('payment_intents/search', {
account: input.account,
secretName: input.secretName,
searchQuery: input.searchQuery,
maxItems: input.maxItems ?? 25,
})
return { hasMore, paymentIntents: items.map(summarizePaymentIntent) }
}
export type ListChargesInput = StripeAuthOptions & {
customerId?: string
paymentIntentId?: string
maxItems?: number
}
/** List charges, optionally scoped to a customer or payment intent. */
export async function listCharges(input: ListChargesInput = {}) {
const { items, hasMore } = await stripeList('charges', {
account: input.account,
secretName: input.secretName,
maxItems: input.maxItems ?? 25,
query: { customer: input.customerId, payment_intent: input.paymentIntentId },
})
return { hasMore, charges: items.map(summarizeCharge) }
}
/** Get one charge by id (full Stripe object). */
export async function getCharge(input: StripeAuthOptions & { chargeId: string }) {
return await stripeRequest({
path: 'charges/' + input.chargeId,
account: input.account,
secretName: input.secretName,
})
}
/**
* Search charges with Stripe's search query language.
* @example searchCharges({ searchQuery: "billing_details.email:'ada@example.com'" })
*/
export async function searchCharges(
input: StripeAuthOptions & { searchQuery: string; maxItems?: number },
) {
const { items, hasMore } = await stripeSearch('charges/search', {
account: input.account,
secretName: input.secretName,
searchQuery: input.searchQuery,
maxItems: input.maxItems ?? 25,
})
return { hasMore, charges: items.map(summarizeCharge) }
}
export type ListRefundsInput = StripeAuthOptions & {
chargeId?: string
paymentIntentId?: string
maxItems?: number
}
/** List refunds, optionally scoped to a charge or payment intent. */
export async function listRefunds(input: ListRefundsInput = {}) {
const { items, hasMore } = await stripeList('refunds', {
account: input.account,
secretName: input.secretName,
maxItems: input.maxItems ?? 25,
query: { charge: input.chargeId, payment_intent: input.paymentIntentId },
})
return {
hasMore,
refunds: items.map((refund) => ({
id: refund.id,
status: refund.status,
amount: refund.amount,
currency: refund.currency,
display: formatStripeAmount(refund.amount, refund.currency),
chargeId: idOf(refund.charge),
paymentIntentId: idOf(refund.payment_intent),
reason: refund.reason ?? null,
created: stripeDate(refund.created),
})),
}
}
export type CreateRefundInput = StripeAuthOptions & {
chargeId?: string
paymentIntentId?: string
/** Amount in smallest currency unit; omit for a full refund. */
amount?: number
reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'
idempotencyKey?: string
/** Refunds move real money; must be true unless dryRun. */
confirm?: boolean
dryRun?: boolean
}
/** Create a refund (full or partial). Requires confirm: true. Pass dryRun: true to preview. */
export async function createRefund(input: CreateRefundInput) {
if (!input.chargeId && !input.paymentIntentId) {
throw new Error('Pass chargeId or paymentIntentId to createRefund.')
}
const body = {
charge: input.chargeId,
payment_intent: input.paymentIntentId,
amount: input.amount,
reason: input.reason,
}
const preview = mutationPreview(input, {
action: 'refund ' + (input.chargeId ?? input.paymentIntentId),
method: 'POST',
path: 'refunds',
body,
requireConfirm: true,
})
if (preview) return preview
return await stripeRequest({
path: 'refunds',
method: 'POST',
account: input.account,
secretName: input.secretName,
idempotencyKey: input.idempotencyKey,
body,
})
}
const paymentActions = [
'list-payment-intents',
'get-payment-intent',
'search-payment-intents',
'list-charges',
'get-charge',
'search-charges',
'list-refunds',
'create-refund',
] as const
/**
* Payments dispatcher. Defaults to list-charges.
*/
export default async function payments(input: Record<string, unknown> = {}) {
const action = parseAction(input.action, paymentActions, 'list-charges', 'payments')
switch (action) {
case 'list-payment-intents':
return await listPaymentIntents(input as ListPaymentIntentsInput)
case 'get-payment-intent':
return await getPaymentIntent(input as never)
case 'search-payment-intents':
return await searchPaymentIntents(input as never)
case 'list-charges':
return await listCharges(input as ListChargesInput)
case 'get-charge':
return await getCharge(input as never)
case 'search-charges':
return await searchCharges(input as never)
case 'list-refunds':
return await listRefunds(input as ListRefundsInput)
case 'create-refund':
return await createRefund(input as CreateRefundInput)
default: {
const exhaustive: never = action
throw new Error('Unhandled payments action: ' + String(exhaustive))
}
}
}