@gj-kit/toss-payments-nestjs
DI token은 타입을 싣고 다니지 않습니다. TossPaymentsFor<typeof config>가 그 타입을 되살려, 배선하지 않은 flow는 그대로 컴파일 에러로 남습니다.
- npm
0.4.5 - 런타임 의존성 0
- @ts-expect-error로 고정한 거부 9건
- Nest 10·11 실제 부팅 검증
- unit 테스트 20개, 그중 8개는 실제 Nest DI 부팅
계속 학습하기
섹션 제목: “계속 학습하기”왜 필요한가
섹션 제목: “왜 필요한가”DI token은 타입을 싣고 다니지 않습니다. forRoot()가 돌려주는 건 unique symbol에 바인딩된 DynamicModule이라, 주입 지점에 적은 생성자 타입 표기가 유일한 근거로 남습니다 — BillingKeyStore를 배선한 적 없는 config인데도 toss.billing이 타입 검사를 통과하고, 런타임에는 그 프로퍼티가 undefined입니다. 여기에 webhook controller를 붙이면 Nest 기본 body parser가 요청을 이미 객체로 바꿔 놓은 뒤입니다. 다시 직렬화한 JSON으로는 서명 검증을 복구할 수 없습니다.
무엇으로 막는가
섹션 제목: “무엇으로 막는가”- 배선 안 한 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 테스트가 그대로 해석됩니다.
Golden path
섹션 제목: “Golden path”완료 상태: typed payment kit을 주입하는 Nest provider 하나를 만듭니다.
1. 설치
섹션 제목: “1. 설치”pnpm add @gj-kit/toss-payments-nestjs2. 앱이 소유할 경계를 정합니다
섹션 제목: “2. 앱이 소유할 경계를 정합니다”core payment config을 만들고 한 번만 등록하며, webhook route에서는 원본 request bytes를 보존합니다.
3. 최소 연결부터 시작합니다
섹션 제목: “3. 최소 연결부터 시작합니다”먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.
import { orThrow } from '@gj-kit/toss-payments';import { defineTossPaymentsConfig, parseApiSecretKey } from '@gj-kit/toss-payments/server';import { TossPaymentsModule } from '@gj-kit/toss-payments-nestjs';
declare const apiSecretFromEnv: string; // Read this once from your server environment.
const config = defineTossPaymentsConfig({ secretKey: orThrow(parseApiSecretKey(apiSecretFromEnv)),});
export const payments = TossPaymentsModule.forRoot(config);실제로는 이렇게 걸립니다
섹션 제목: “실제로는 이렇게 걸립니다”주석 처리한 줄은 실제 TS2339입니다. 이 config에는 BillingKeyStore가 배선되어 있지 않으므로, 타입을 싣지 않는 DI token을 거쳐 주입된 kit에도 billing 프로퍼티 자체가 존재하지 않습니다.
import { Injectable } from '@nestjs/common';import { orThrow } from '@gj-kit/toss-payments';import { defineTossPaymentsConfig, parseApiSecretKey, type OrderStore } from '@gj-kit/toss-payments/server';import { InjectTossPayments, TossPaymentsModule, type TossPaymentsFor } from '@gj-kit/toss-payments-nestjs';
declare const SECRET: string; // the app owns this (process.env)declare const orders: OrderStore; // the app owns this (its own DB adapter)
export const tossConfig = defineTossPaymentsConfig({ secretKey: orThrow(parseApiSecretKey(SECRET)), orders });export type AppToss = TossPaymentsFor<typeof tossConfig>;export const tossModule = TossPaymentsModule.forRoot(tossConfig);
@Injectable()export class PaymentsService { constructor(@InjectTossPayments() private readonly toss: AppToss) {}
order = () => this.toss.confirm.createOrder({ amount: 9_900, orderName: 'Pro' }); // this.toss.billing — TS2339: the config wired no BillingKeyStore, so there is no property.}사용할 때
섹션 제목: “사용할 때”Nest 앱에서 core payment kit의 타입과 안전 경계를 의존성 주입까지 유지해야 할 때 사용합니다.
사용하지 않을 때
섹션 제목: “사용하지 않을 때”controller에서 결제 검증을 다시 구현하거나 웹훅 검증에 raw bytes가 필요한데 파싱된 JSON에 의존하지 마세요.
환경과 peer
섹션 제목: “환경과 peer”엔진: node >=20
| Peer | 지원 범위 | 필요한 위치 |
|---|---|---|
@gj-kit/toss-payments |
`^0.2.0 | |
@nestjs/common |
`^10 | |
reflect-metadata |
`^0.1.13 | |
rxjs |
^7 |
패키지 통합 |
공개 subpath
섹션 제목: “공개 subpath”| Entry | Exported symbols |
|---|---|
Root entry |
14 |
안전 경계
섹션 제목: “안전 경계”검증되는 웹훅은 원본 request bytes를 보존하고, 모든 store 의존성을 host Nest module에 명시하세요.
API reference
섹션 제목: “API reference”전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.
패키지 관계
섹션 제목: “패키지 관계”| 관계 | 패키지 | 함께 쓰는 이유 |
|---|---|---|
| 필수 | @gj-kit/toss-payments |
이 패키지의 공개 계약을 구현하거나 사용합니다. |
| 선택 조합 | @gj-kit/toss-payments-postgresql |
호스트가 PostgreSQL을 선택했다면 Nest module에 PostgreSQL 기반 core store를 제공합니다. |