Overview
The @1stdigital/prism-nestjs package provides a full-featured NestJS module with decorators, guards, and interceptors for payment-protecting your API routes.Decorator-Based
@Payment() and @Payer() decorators
Module System
PrismModule.forRoot() configuration
Dependency Injection
Works with NestJS DI system
Installation
- npm
- yarn
- pnpm
Copy
npm install @1stdigital/prism-nestjs @nestjs/core @nestjs/common
bash yarn add @1stdigital/prism-nestjs @nestjs/core @nestjs/common Copy
pnpm add @1stdigital/prism-nestjs @nestjs/core @nestjs/common
Quick Start
Copy
// app.module.ts
import { Module } from "@nestjs/common";
import { PrismModule } from "@1stdigital/prism-nestjs";
import { AppController } from "./app.controller";
@Module({
imports: [
PrismModule.forRoot({
apiKey: process.env.PRISM_API_KEY || "dev-key-123",
baseUrl: "https://prism-api.test.1stdigital.tech",
routes: {
"/api/premium": {
price: 0.01, // $0.01 USD
description: "Premium API",
},
},
}),
],
controllers: [AppController],
})
export class AppModule {}
// app.controller.ts
import { Controller, Get } from "@nestjs/common";
import { Payment, Payer } from "@1stdigital/prism-nestjs";
@Controller("api")
export class AppController {
@Get("premium")
@Payment() // Requires payment!
getPremiumData(@Payer() payer: string) {
return {
message: "Premium content",
payer,
};
}
}
Configuration
Module Options
Copy
PrismModule.forRoot({
apiKey: "your-api-key",
baseUrl: "https://prism-gateway.com",
debug: true,
routes: {
"/api/premium": {
price: 0.01,
description: "Premium API",
},
"/api/data/*": {
price: 0.005,
description: "Data API (wildcard)",
},
},
});
Decorators
@Payment() Decorator
Copy
import { Controller, Get } from "@nestjs/common";
import { Payment } from "@1stdigital/prism-nestjs";
@Controller("api")
export class DataController {
// Payment required
@Get("premium")
@Payment()
getPremium() {
return { data: "Premium content" };
}
// No payment required
@Get("free")
getFree() {
return { data: "Free content" };
}
}
@Payer() Decorator
Copy
import { Controller, Get } from "@nestjs/common";
import { Payment, Payer } from "@1stdigital/prism-nestjs";
@Controller("api")
export class DataController {
@Get("premium")
@Payment()
getPremium(@Payer() payer: string) {
// payer = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
return {
message: "Premium data",
payer,
};
}
}
Accessing Payment Info
Request Context
Copy
import { Controller, Get, Req } from "@nestjs/common";
import { Payment } from "@1stdigital/prism-nestjs";
import { Request } from "express";
@Controller("api")
export class DataController {
@Get("premium")
@Payment()
getPremium(@Req() request: Request) {
// Access via request object
const payer = request.payer;
const payment = request.locals?.payment;
return {
message: "Premium data",
payer,
network: payment?.network,
};
}
}
Settlement Validation
NestJS uses an Interceptor to validate settlement:Copy
// Internal implementation (automatic!)
@Injectable()
export class PrismSettlementInterceptor implements NestInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
mergeMap(async (data) => {
const response = context.switchToHttp().getResponse();
const settlementResult = await this.settlementCallback(...);
if (!settlementResult || !settlementResult.success) {
// ❌ Settlement failed
response.status(402);
return {
error: 'Payment settlement failed',
details: settlementResult?.errorReason
};
}
// ✅ Settlement succeeded
response.header('X-PAYMENT-RESPONSE', settlementResult.transaction);
return data;
})
);
}
}
Testing
Copy
import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication } from "@nestjs/common";
import * as request from "supertest";
import { AppModule } from "./app.module";
describe("PrismModule (e2e)", () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it("returns 402 without payment", () => {
return request(app.getHttpServer())
.get("/api/premium")
.expect(402)
.expect((res) => {
expect(res.body.paymentRequired).toBe(true);
});
});
it("returns 200 with valid payment", () => {
const payment = JSON.stringify({
scheme: "eip3009",
signature: "0x" + "0".repeat(130),
});
return request(app.getHttpServer())
.get("/api/premium")
.set("X-PAYMENT", payment)
.expect(200)
.expect((res) => {
expect(res.body.message).toBe("Premium content");
});
});
});
Production Deployment
Copy
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: ["error", "warn", "log"],
});
// Enable CORS if needed
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(","),
credentials: true,
});
const port = parseInt(process.env.PORT || "3000");
await app.listen(port);
console.log(`NestJS + Prism running on port ${port}`);
}
bootstrap();