@gj-kit/toss-payments-nestjs
A DI token carries no type — TossPaymentsFor<typeof config> gives it back, so unwired flows stay compile errors.
- npm
0.4.5 - 0 runtime dependencies
- 9 rejections pinned by @ts-expect-error
- Nest 10 and 11 boot-verified
- 20 unit tests, 8 boot real Nest DI
Keep learning
Section titled “Keep learning”Why this exists
Section titled “Why this exists”A DI token carries no type: forRoot() returns a DynamicModule bound to a unique symbol, so the constructor annotation you wrote is the only remaining truth — toss.billing type-checks on a kit whose config never wired a BillingKeyStore, and the property is undefined at runtime. Then you add a webhook controller and Nest has already parsed the body: signature verification cannot be recovered from re-serialized JSON.
What it does about it
Section titled “What it does about it”- Unwired flows have no property —
TossPaymentsFor<typeof config>rebuilds the conditional kit type after DI, sotoss.billingis a compile error without a wiredBillingKeyStore. - Missing rawBody fails loudly — Without
req.rawBody,toNestWebhookHandlernever calls a handler: it answers 500 and logs the three settings to check —rawBody: trueonNestFactory.create, Fastify raw-body support, and a JSON middleware applied ahead of the webhook route. - Source IP survives the wrapper — The handler forwards the original Node
socket, keeping the core’s fail-closed IP check; trusting a proxy header requires passing an explicitsourceIpextractor. - One token across ESM and CJS —
TOSS_PAYMENTSandgetTossPaymentsToken(name)areSymbol.forlookups, so a dual-loaded package still resolves to one provider binding. - No emitDecoratorMetadata needed —
InjectTossPayments()is a thin@Inject(token)delegate and no code undersrc/readsdesign:paramtypes, so the package shipsemitDecoratorMetadata: false— and the Nest DI tests still resolve under vitest’s esbuild transform, which cannot emit that metadata at all.
Golden path
Section titled “Golden path”Outcome: One Nest provider that injects a typed payment kit.
1. Install
Section titled “1. Install”pnpm add @gj-kit/toss-payments-nestjs2. Keep the app-owned boundary explicit
Section titled “2. Keep the app-owned boundary explicit”Build the core payment config, register it once, and preserve raw request bytes for webhook routes.
3. Start with the smallest integration
Section titled “3. Start with the smallest integration”Copy this first, then replace only the app-owned values named above.
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);What that looks like
Section titled “What that looks like”The commented line is a real TS2339: this config wired no BillingKeyStore, so the injected kit has no billing property at all — even though the value arrived through an untyped DI token.
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.}Use it when
Section titled “Use it when”Use it when a Nest application needs to keep the core payment kit’s types and safety boundary through dependency injection.
Do not use it when
Section titled “Do not use it when”Do not reimplement payment verification in controllers or rely on parsed JSON when webhook verification requires raw bytes.
Runtime and peers
Section titled “Runtime and peers”Engines: node >=20
| Peer | Supported range | Required for |
|---|---|---|
@gj-kit/toss-payments |
`^0.2.0 | |
@nestjs/common |
`^10 | |
reflect-metadata |
`^0.1.13 | |
rxjs |
^7 |
the package integration |
Public subpaths
Section titled “Public subpaths”| Entry | Exported symbols |
|---|---|
Root entry |
14 |
Safety boundary
Section titled “Safety boundary”Preserve raw request bytes for verified webhooks and make every store dependency explicit in the host Nest module.
API reference
Section titled “API reference”complete API reference contains every public function, class, type, constant, and error contract generated from release declarations. Each item shows its import path, release signature, and package version.
Package relationships
Section titled “Package relationships”| Relationship | Package | Why combine them |
|---|---|---|
| Required | @gj-kit/toss-payments |
This package implements or consumes its public contract. |
| Optional companion | @gj-kit/toss-payments-postgresql |
Supply PostgreSQL-backed core stores to the Nest module when the host chooses PostgreSQL. |