공유 Stripe 클라이언트와 서명 검증·멱등 처리되는 단일 웹훅, 각 모듈로 이벤트 전달
코드파일 8개컨텍스트약 545토큰MCP stripe검사 통과
설치
$
genpm add @core/stripe포함 내용
- src/lib/stripe/에 소스 코드, 파일 8개. (7.8kB)
- src/lib/stripe/AGENTS.md에 AI 규칙, 그리고 IDE 규칙 파일.
- .env.example에 추가되는 환경 변수: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET.
- @core/db을(를) 자동으로 해결합니다.
README
이 패키지에는 README가 없습니다.
약 545토큰→ src/lib/stripe/AGENTS.md→ .cursor/rules/genpm-core-stripe.mdc
이것이 AI가 src/lib/stripe에서 작업할 때 읽는 내용 그대로입니다. 그 외에는 컨텍스트에 아무것도 추가되지 않습니다.
@core/stripe — rules for AI agents
Purpose
The single Stripe integration point of the app: a lazily created client (getStripe) and ONE webhook endpoint that
verifies the signature, records each event in stripe_events (idempotency) and dispatches it, inside one database
transaction, to the handlers that modules register with onStripeEvent (@core/billing 2.x, @core/checkout…).
If a handler throws, everything rolls back and Stripe retries. No business logic here.
Map
index.ts— public API:getStripe,setStripe,onStripeEvent,handleStripeWebhook,stripeEvents.webhooks.ts— verification, idempotency and dispatch.client.ts— client.schema.ts—stripe_events.adapters/hono.ts—stripeWebhookRoutes().adapters/next.ts—stripeWebhookRoute.
Integration
- Env:
STRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRET(whsec_…). Migrations as insrc/lib/db/AGENTS.md. - Mount the endpoint: Hono
app.route('/api/stripe/webhook', stripeWebhookRoutes()); Next.jsapp/api/stripe/webhook/route.ts→export { stripeWebhookRoute as POST } from '@/lib/stripe/adapters/next'. - In Stripe, create one webhook endpoint
<origin>/api/stripe/webhookwith the events the installed modules list in their AGENTS.md. Locally:stripe listen --forward-to localhost:3000/api/stripe/webhook. - Import the modules that register handlers at startup (their
index.tsregisters them on import). - Verify:
stripe trigger checkout.session.completedreturns 200 and a row appears instripe_events. - Optional MCP: with consent, the agent can use Stripe's MCP server to look up products and prices in test mode.
Conventions
- Handlers write only through the
txthey receive and must be idempotent. - Read the current object from the API when order matters (events can arrive out of order).
- @core/billing 1.x also defines
stripe_events; upgrade it to 2.x before installing this module.
Don't
- Don't create more webhook endpoints or call
constructEventelsewhere. - Don't parse the webhook body as JSON before verification; the signature covers the raw bytes.
- Don't log full event payloads, card data or keys.
# @core/stripe — rules for AI agents
## Purpose
The single Stripe integration point of the app: a lazily created client (`getStripe`) and ONE webhook endpoint that
verifies the signature, records each event in `stripe_events` (idempotency) and dispatches it, inside one database
transaction, to the handlers that modules register with `onStripeEvent` (@core/billing 2.x, @core/checkout…).
If a handler throws, everything rolls back and Stripe retries. No business logic here.
## Map
- `index.ts` — public API: `getStripe`, `setStripe`, `onStripeEvent`, `handleStripeWebhook`, `stripeEvents`.
- `webhooks.ts` — verification, idempotency and dispatch. `client.ts` — client. `schema.ts` — `stripe_events`.
- `adapters/hono.ts` — `stripeWebhookRoutes()`. `adapters/next.ts` — `stripeWebhookRoute`.
## Integration
1. Env: `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` (`whsec_…`). Migrations as in `src/lib/db/AGENTS.md`.
2. Mount the endpoint: Hono `app.route('/api/stripe/webhook', stripeWebhookRoutes())`; Next.js
`app/api/stripe/webhook/route.ts` → `export { stripeWebhookRoute as POST } from '@/lib/stripe/adapters/next'`.
3. In Stripe, create one webhook endpoint `<origin>/api/stripe/webhook` with the events the installed modules list in
their AGENTS.md. Locally: `stripe listen --forward-to localhost:3000/api/stripe/webhook`.
4. Import the modules that register handlers at startup (their `index.ts` registers them on import).
5. Verify: `stripe trigger checkout.session.completed` returns 200 and a row appears in `stripe_events`.
6. Optional MCP: with consent, the agent can use Stripe's MCP server to look up products and prices in test mode.
## Conventions
- Handlers write only through the `tx` they receive and must be idempotent.
- Read the current object from the API when order matters (events can arrive out of order).
- @core/billing 1.x also defines `stripe_events`; upgrade it to 2.x before installing this module.
## Don't
- Don't create more webhook endpoints or call `constructEvent` elsewhere.
- Don't parse the webhook body as JSON before verification; the signature covers the raw bytes.
- Don't log full event payloads, card data or keys.
.genpmignore 적용 후 주입될 정확한 트리입니다. 고정 대상:
// 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 };
});
}
- 서버
- stripe
- 명령
- npx -y @stripe/mcp
- env
- STRIPE_SECRET_KEY
| 버전 | 커밋 | 게시일 | 검사 |
|---|---|---|---|
| 1.0.0 | 43dfe91 | 5시간 전 | 검사 통과 |
- genpm
- @core/db ^1.0.0
- 제안됨
- GenPM은 npm 명령을 제안하고, 동의한 경우에만 실행합니다.
- 사용하는 패키지 (2)
- @core/billing ^1.0.0@core/checkout ^1.0.0
- 검사
- 검사 통과 · 문제 0건
- 커밋
- v1.0.0 → 43dfe91b82752ce063e8399b4af8446da14236c1 · 가져온 뒤 검증됨
- 스크립트
- 없음. GenPM은 패키지 코드를 절대 실행하지 않습니다.
- 라이선스
- MIT
- 품질
- 100/100
- 인정된 라이선스충족
- AGENTS.md에 목적 설명충족
- AGENTS.md에 통합 단계충족
- AGENTS.md에 규칙 또는 금지 사항충족
- 테스트 포함충족
- 보안 검사 통과충족
- 최근 6개월 내 게시충족
- 인증된 게시자충족
- 요약과 키워드충족
- 신고
- 문제가 있나요?