@gj-kit/toss-payments-nestjs 핵심 개념
DI token은 타입을 싣고 다니지 않습니다. TossPaymentsFor<typeof config>가 그 타입을 되살려, 배선하지 않은 flow는 그대로 컴파일 에러로 남습니다.
왜 이 경계가 필요한가
섹션 제목: “왜 이 경계가 필요한가”DI token은 타입을 싣고 다니지 않습니다. forRoot()가 돌려주는 건 unique symbol에 바인딩된 DynamicModule이라, 주입 지점에 적은 생성자 타입 표기가 유일한 근거로 남습니다 — BillingKeyStore를 배선한 적 없는 config인데도 toss.billing이 타입 검사를 통과하고, 런타임에는 그 프로퍼티가 undefined입니다. 여기에 webhook controller를 붙이면 Nest 기본 body parser가 요청을 이미 객체로 바꿔 놓은 뒤입니다. 다시 직렬화한 JSON으로는 서명 검증을 복구할 수 없습니다.
Nest DI는 core kit type을 보존해야 합니다
섹션 제목: “Nest DI는 core kit type을 보존해야 합니다”generic DI token은 configuration별 capability를 잃습니다. 이 패키지는 주입 지점에서 등록한 configuration으로 typed core kit을 다시 만듭니다.
webhook 검증에는 raw bytes가 필요합니다
섹션 제목: “webhook 검증에는 raw bytes가 필요합니다”Nest JSON middleware가 요청을 파싱한 뒤에는 재직렬화로 signature input을 복구할 수 없습니다. raw-body 설정은 선행 조건입니다.
패키지가 강제하는 것
섹션 제목: “패키지가 강제하는 것”- 배선 안 한 flow는 프로퍼티가 없습니다 —
TossPaymentsFor<typeof config>가 DI 경계 뒤에서도 조건부 kit 타입을 복원하므로,BillingKeyStore배선 없이toss.billing을 쓰면 컴파일 에러입니다. - rawBody 부재는 조용히 넘어가지 않습니다 —
req.rawBody가 없으면toNestWebhookHandler는 handler를 호출하지 않습니다. 500을 돌려주고 확인할 설정 세 가지를 로그로 남깁니다 —NestFactory.create의rawBody: true, Fastify의 raw body 지원 설정, 그리고 webhook route 앞에 걸린 JSON 미들웨어. - source IP가 wrapper를 통과합니다 — handler가 원본 Node
socket을 그대로 전달해 코어의 fail-closed IP 검증이 유지되고, proxy 헤더를 신뢰하려면sourceIpextractor를 명시해야 합니다. - ESM·CJS 어디서든 같은 token —
TOSS_PAYMENTS와getTossPaymentsToken(name)은Symbol.for기반이라, 패키지가 ESM/CJS로 이중 로드돼도 provider 바인딩이 하나로 유지됩니다. - emitDecoratorMetadata 없이 동작합니다 —
InjectTossPayments()는@Inject(token)에 위임하는 얇은 래퍼이고src/어디에도design:paramtypes를 읽는 코드가 없습니다. 그래서 패키지는emitDecoratorMetadata: false로 배포되며, 해당 메타데이터를 아예 만들지 못하는 vitest의 esbuild 변환에서도 Nest DI 테스트가 그대로 해석됩니다.
앱에 남는 책임
섹션 제목: “앱에 남는 책임”검증되는 웹훅은 원본 request bytes를 보존하고, 모든 store 의존성을 host Nest module에 명시하세요.
다음은 검증된 레시피 두 개입니다.