KO
베타 번역

@core / stripe

1.0.0 ▾
인증됨MIT
GitHub

공유 Stripe 클라이언트와 서명 검증·멱등 처리되는 단일 웹훅, 각 모듈로 이벤트 전달

코드파일 8개컨텍스트약 545토큰MCP stripe검사 통과

.genpmignore 적용 후 주입될 정확한 트리입니다. 고정 대상:

src/lib/stripe/webhooks.ts읽기 전용 · 43dfe91
// Un único endpoint de webhooks para toda la app: verifica la firma, registra el evento (idempotencia) y lo reparte
// a los manejadores de cada módulo (billing, checkout…) dentro de la misma transacción.
import type Stripe from 'stripe';
import { type Executor, withTransaction } from '../db/index.ts';
import { getStripe } from './client.ts';
import { stripeEvents } from './schema.ts';

export type StripeEventHandler = (event: Stripe.Event, tx: Executor) => Promise<void>;

const handlers = new Map<string, StripeEventHandler[]>();

/**
 * Registra un manejador para uno o varios tipos de evento (`checkout.session.completed`…). Debe ser idempotente y usar
 * `tx` para escribir: si lanza, se deshace todo y Stripe reintenta.
 */
export function onStripeEvent(types: Stripe.Event.Type | Stripe.Event.Type[], handler: StripeEventHandler): void {
  for (const t of [types].flat()) handlers.set(t, [...(handlers.get(t) ?? []), handler]);
}

/** Solo tests. */
export const clearStripeHandlers = () => handlers.clear();

export class WebhookSignatureError extends Error {}

export type WebhookResult = { received: true; duplicate: boolean; type: string; handled: number };

export async function handleStripeWebhook(rawBody: string, signature: string | null): Promise<WebhookResult> {
  const secret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!secret) throw new Error('STRIPE_WEBHOOK_SECRET is not set');
  if (!signature) throw new WebhookSignatureError('missing stripe-signature header');
  let event: Stripe.Event;
  try {
    event = await getStripe().webhooks.constructEventAsync(rawBody, signature, secret);
  } catch {
    throw new WebhookSignatureError('invalid signature');
  }
  return withTransaction(async (tx) => {
    const inserted = await tx.insert(stripeEvents).values({ id: event.id, type: event.type }).onConflictDoNothing().returning({ id: stripeEvents.id });
    if (inserted.length === 0) return { received: true, duplicate: true, type: event.type, handled: 0 };
    const list = handlers.get(event.type) ?? [];
    for (const h of list) await h(event, tx);
    return { received: true, duplicate: false, type: event.type, handled: list.length };
  });
}

@core/stripe 신고

패키지를 신고하려면 GitHub로 로그인하세요.