npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@reyco1/nestjs-stripe

v2.0.1

Published

A NestJS module for Stripe integration supporting one-time payments and subscriptions

Readme

@reyco1/nestjs-stripe

A powerful NestJS module for Stripe integration that supports both one-time payments and subscriptions. This package provides a seamless way to integrate Stripe payment processing into your NestJS application.

Table of Contents

Overview 🛠️

When installed, this package will:

  1. Add required imports to your app.module.ts:

    • ConfigService from @nestjs/config
    • StripeModule from @reyco1/nestjs-stripe
  2. Configure the StripeModule with async configuration using ConfigService

  3. Add necessary environment variables to your .env and .env.example files:

    STRIPE_API_KEY=your_stripe_secret_key
    STRIPE_API_VERSION=your_stripe_api_version
    STRIPE_WEBHOOK_SECRET=your_webhook_secret

Features ✨

  • 💳 One-time payment processing
  • 🔄 Subscription management
  • 🛍️ Stripe Checkout integration
  • 👥 Customer management
  • 🎣 Webhook handling
  • 📝 TypeScript support
  • 🔌 Auto-configuration setup
  • 🔧 Environment variables management
  • 🛠️ Comprehensive utility methods
  • 🔍 Type-safe interfaces
  • 💪 Enhanced data handling and validation
  • 📊 Detailed payment information extraction
  • 🔐 Secure webhook processing

Installation 📦

# Install the package
npm install @reyco1/nestjs-stripe

# Run the configuration script (if automatic setup didn't run)
npx @reyco1/nestjs-stripe

Basic Usage 💡

Using StripeService (Core Operations)

@Injectable()
export class PaymentService {
  constructor(private readonly stripeService: StripeService) {}
  
  async createPayment() {
    return this.stripeService.createPaymentIntent({
      amount: 1000,
      currency: 'usd'
    });
  }
}

Using StripeUtils (Enhanced Data Handling)

@Injectable()
export class PaymentService {
  constructor(private readonly stripeUtils: StripeUtils) {}

  async getPaymentDetails(paymentIntent: Stripe.PaymentIntent) {
    const [customerDetails, paymentMethod, refundInfo] = await Promise.all([
      this.stripeUtils.getCustomerDetails(paymentIntent),
      this.stripeUtils.getPaymentMethodDetails(paymentIntent),
      this.stripeUtils.getRefundInfo(paymentIntent)
    ]);

    return {
      customer: customerDetails,
      payment: paymentMethod,
      refunds: refundInfo,
      amount: this.stripeUtils.formatAmount(paymentIntent.amount)
    };
  }
}

Using Raw Stripe Client

@Injectable()
export class PaymentService {
  constructor(
    @Inject(STRIPE_CLIENT_TOKEN) private readonly stripeClient: Stripe
  ) {}
  
  async createPayment() {
    return this.stripeClient.paymentIntents.create({
      amount: 1000,
      currency: 'usd'
    });
  }
}

Configuration ⚙️

Module Configuration

// app.module.ts
import { StripeModule } from '@reyco1/nestjs-stripe';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    StripeModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        apiKey: configService.get('STRIPE_API_KEY'),
        apiVersion: configService.get('STRIPE_API_VERSION'),
        webhookSecret: configService.get('STRIPE_WEBHOOK_SECRET'),
      }),
    }),
  ],
})
export class AppModule {}

Checkout Sessions 🛍️

Payment Checkout

Create one-time payment checkout sessions:

const session = await stripeService.createPaymentCheckoutSession({
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  lineItems: [{
    price: 'price_H5ggYwtDq4fbrJ',
    quantity: 1
  }],
  // Or create a product on the fly:
  // lineItems: [{
  //   name: 'T-shirt',
  //   amount: 2000,
  //   currency: 'usd',
  //   quantity: 1
  // }],
  paymentMethodTypes: ['card'],
  shippingAddressCollection: {
    allowed_countries: ['US', 'CA']
  },
  billingAddressCollection: 'required',
  customerCreation: 'if_required'
});

Subscription Checkout

Create subscription checkout sessions:

const session = await stripeService.createSubscriptionCheckoutSession({
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  lineItems: [{
    price: 'price_H5ggYwtDq4fbrJ', // recurring price ID
    quantity: 1
  }],
  paymentMethodTypes: ['card'],
  trialPeriodDays: 14,
  subscriptionData: {
    description: 'Premium Plan Subscription',
    metadata: {
      plan: 'premium'
    }
  },
  customerCreation: 'if_required'
});

Customer Creation Behavior

The customer creation behavior in checkout sessions depends on how you configure the customerId and customerCreation parameters:

  1. Using Existing Customer
await stripeService.createPaymentCheckoutSession({
  customerId: 'cus_123...', // Will use this customer
  customerCreation: 'always', // This will be ignored
  // ... other params
});
  1. New Customer for One-time Payment
await stripeService.createPaymentCheckoutSession({
  customerCreation: 'always', // Will create new customer
  // ... other params
});
  1. New Customer for Subscription
await stripeService.createSubscriptionCheckoutSession({
  customerCreation: 'if_required', // Will create customer since needed for subscriptions
  // ... other params
});
  1. Default Behavior
  • For one-time payments: Customer is only created if specifically requested
  • For subscriptions: Customer is always created if not provided
  • When customerId is provided: Existing customer is used and customerCreation is ignored

Configuration Options

Common configuration options for checkout sessions:

interface CheckoutSessionOptions {
  // Required parameters
  successUrl: string;          // Redirect after successful payment
  cancelUrl: string;           // Redirect if customer cancels
  lineItems: LineItem[];       // Products/prices to charge

  // Customer handling
  customerId?: string;         // Existing customer ID
  customerEmail?: string;      // Pre-fill customer email
  customerCreation?: 'always' | 'if_required';

  // Payment configuration
  paymentMethodTypes?: PaymentMethodType[]; // e.g., ['card', 'sepa_debit']
  allowPromotionCodes?: boolean;
  
  // Address collection
  billingAddressCollection?: 'required' | 'auto';
  shippingAddressCollection?: {
    allowed_countries: string[]; // e.g., ['US', 'CA']
  };

  // Customization
  locale?: string;             // e.g., 'auto' or 'en'
  submitType?: 'auto' | 'pay' | 'book' | 'donate';
  
  // Additional data
  metadata?: Record<string, string | number>;
  clientReferenceId?: string;
}

Utility Methods 🛠️

Customer Details

const customerDetails = await stripeUtils.getCustomerDetails(paymentIntent);
// Returns:
{
  customerId: string;
  email?: string;
  name?: string;
  phone?: string;
  metadata?: Record<string, string>;
}

Payment Method Details

const paymentMethod = await stripeUtils.getPaymentMethodDetails(paymentIntent);
// Returns:
{
  id?: string;
  type?: string;
  last4?: string;
  brand?: string;
  expMonth?: number;
  expYear?: number;
  billingDetails?: {
    name?: string;
    email?: string;
    phone?: string;
    address?: Stripe.Address;
  };
  metadata?: Record<string, string>;
}

Refund Information

const refundInfo = await stripeUtils.getRefundInfo(paymentIntent);
// Returns:
{
  refunded: boolean;
  refundedAmount?: number;
  refundCount?: number;
  refunds?: Array<{
    id: string;
    amount: number;
    status: string;
    reason?: string;
    created: Date;
    metadata?: Record<string, string>;
  }>;
}

Subscription Details

const subscription = await stripeUtils.getSubscriptionDetails(subscriptionId);
// Returns:
{
  id: string;
  status: string;
  currentPeriodStart: Date;
  currentPeriodEnd: Date;
  trialStart?: Date;
  trialEnd?: Date;
  cancelAt?: Date;
  canceledAt?: Date;
  endedAt?: Date;
  metadata?: Record<string, string>;
  items?: Array<{
    id: string;
    priceId: string;
    quantity?: number;
    metadata?: Record<string, string>;
  }>;
}

Amount Formatting

const formattedAmount = stripeUtils.formatAmount(1000, 'usd');
// Returns: "$10.00"

Payment Operations 💳

Creating One-Time Payments

@Injectable()
export class PaymentService {
  constructor(
    private readonly stripeService: StripeService,
    private readonly stripeUtils: StripeUtils
  ) {}

  async createPayment() {
    const payment = await this.stripeService.createPaymentIntent({
      amount: 1000,
      currency: 'usd',
      metadata: {
        orderId: 'ORDER_123'
      }
    });

    // Get comprehensive payment details
    const details = await this.stripeUtils.getPaymentMethodDetails(payment);
    const customer = await this.stripeUtils.getCustomerDetails(payment);

    return {
      payment,
      details,
      customer,
      formattedAmount: this.stripeUtils.formatAmount(payment.amount)
    };
  }
}

Subscription Management 📅

@Injectable()
export class SubscriptionService {
  constructor(
    private readonly stripeService: StripeService,
    private readonly stripeUtils: StripeUtils
  ) {}

  async createSubscription(customerId: string, priceId: string) {
    const subscription = await this.stripeService.createSubscription({
      customerId,
      priceId,
      metadata: {
        plan: 'premium'
      }
    });

    return this.stripeUtils.getSubscriptionDetails(subscription.id);
  }

  async cancelSubscription(subscriptionId: string) {
    return this.stripeService.cancelSubscription(subscriptionId);
  }
}

Webhook Handling 🎣

1. Add the module to your application

In your app.module.ts (or appropriate module):

import { Module } from '@nestjs/common';
import { StripeModule, StripeWebhookModule } from '@reyco1/nestjs-stripe';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    StripeModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        apiKey: configService.get('STRIPE_API_KEY'),
        apiVersion: configService.get('STRIPE_API_VERSION'),
        webhookSecret: configService.get('STRIPE_WEBHOOK_SECRET'),
      }),
    }),
    StripeWebhookModule.forRoot(),
    // ... other modules
  ],
})
export class AppModule {}

2. Configure your NestJS application to handle raw body data

In your main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as bodyParser from 'body-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Important: Configure raw body parser for Stripe webhooks
  app.use(
    bodyParser.json({
      verify: (req: any, res, buf) => {
        if (req.originalUrl.startsWith('/stripe/webhook')) {
          req.rawBody = buf;
        }
      },
    })
  );
  
  await app.listen(3000);
}
bootstrap();

3. Create services with webhook handlers

Create services with methods decorated with @StripeWebhookHandler:

import { Injectable, Logger } from '@nestjs/common';
import { StripeWebhookHandler } from '@reyco1/nestjs-stripe';
import Stripe from 'stripe';

@Injectable()
export class SubscriptionService {
  private readonly logger = new Logger(SubscriptionService.name);

  @StripeWebhookHandler('customer.subscription.created')
  async handleSubscriptionCreated(event: Stripe.Event): Promise<void> {
    const subscription = event.data.object as Stripe.Subscription;
    // Process subscription creation
  }

  @StripeWebhookHandler('customer.subscription.updated')
  async handleSubscriptionUpdate(event: Stripe.Event): Promise<void> {
    const subscription = event.data.object as Stripe.Subscription;
    // Process subscription update
  }

  @StripeWebhookHandler('customer.subscription.deleted')
  async handleSubscriptionDelete(event: Stripe.Event): Promise<void> {
    const subscription = event.data.object as Stripe.Subscription;
    // Process subscription deletion
  }
}

4. Register your services in a module

import { Module } from '@nestjs/common';
import { SubscriptionService } from './subscription.service';

@Module({
  providers: [SubscriptionService],
})
export class SubscriptionsModule {}

Features

  • Declarative Approach: Use the @StripeWebhookHandler decorator to specify which methods handle which Stripe events.
  • Automatic Discovery: The module automatically discovers and registers all webhook handlers during application bootstrap.
  • Multiple Handlers: Multiple methods can handle the same event type.
  • Type Safety: Fully typed with TypeScript, leveraging Stripe's TypeScript definitions.
  • Error Handling: Built-in error handling with detailed logging.
  • Signature Verification: Automatically verifies Stripe webhook signatures.

Best Practices

  1. Service Organization: Group related webhook handlers in dedicated services (e.g., SubscriptionService, PaymentService).
  2. Error Handling: Add try/catch blocks in your handlers to gracefully handle errors.
  3. Idempotency: Implement idempotency checks to handle potential duplicate webhook events from Stripe.
  4. Testing: Use Stripe's webhook testing tools to simulate webhook events.

Available Webhook Events

Here are some common Stripe webhook events you might want to handle:

Customer & Subscription Events

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.created
  • customer.updated
  • customer.deleted

Payment & Invoice Events

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • invoice.paid
  • invoice.payment_failed
  • charge.succeeded
  • charge.failed
  • charge.refunded

Checkout Events

  • checkout.session.completed
  • checkout.session.expired

Check the Stripe API documentation for a complete list of event types.

Connected Accounts 🌐

The Connected Accounts module allows you to create and manage Stripe Connect accounts, enabling platforms to facilitate payments between customers and service providers/merchants.

Basic Usage

@Injectable()
export class MerchantService {
  constructor(private readonly connectedAccountsService: ConnectedAccountsService) {}
  
  async onboardMerchant(merchantData: CreateConnectedAccountDto) {
    // 1. Create a connected account
    const account = await this.connectedAccountsService.createConnectedAccount({
      email: merchantData.email,
      country: merchantData.country,
      capabilities: {
        card_payments: { requested: true },
        transfers: { requested: true }
      }
    });
    
    // 2. Generate an onboarding link
    const accountLink = await this.connectedAccountsService.createAccountLink({
      accountId: account.id,
      refreshUrl: 'https://example.com/onboarding/refresh',
      returnUrl: 'https://example.com/onboarding/complete',
      type: 'account_onboarding'
    });
    
    return {
      accountId: account.id,
      onboardingUrl: accountLink.url
    };
  }
}

Adding Bank Accounts

async addBankAccount(accountId: string, bankData: CreateBankAccountDto) {
  return this.connectedAccountsService.createBankAccount(accountId, {
    country: bankData.country,
    currency: bankData.currency,
    accountNumber: bankData.accountNumber,
    routingNumber: bankData.routingNumber,
    accountHolderName: bankData.accountHolderName
  });
}

Processing Payments for Connected Accounts

// Create a payment that automatically transfers funds to the connected account
async processPayment(accountId: string, amount: number) {
  return this.connectedAccountsService.createPaymentIntent(
    amount,
    'usd',
    accountId,
    150 // $1.50 platform fee
  );
}

// Create a transfer to a connected account
async transferFunds(accountId: string, amount: number) {
  return this.connectedAccountsService.createTransfer(
    amount,
    'usd',
    accountId,
    { description: 'Monthly payout' }
  );
}

Creating Checkout Sessions for Connected Accounts

async createConnectedCheckout(accountId: string) {
  return this.connectedAccountsService.createConnectedAccountCheckoutSession({
    connectedAccountId: accountId,
    applicationFeeAmount: 250, // $2.50 platform fee
    successUrl: 'https://example.com/success',
    cancelUrl: 'https://example.com/cancel',
    lineItems: [{
      name: 'Service fee',
      amount: 5000, // $50.00
      currency: 'usd',
      quantity: 1
    }]
  });
}

Managing Connected Accounts

// List all connected accounts
async getAllMerchants(limit = 10, startingAfter?: string) {
  return this.connectedAccountsService.listConnectedAccounts(limit, startingAfter);
}

// Update a connected account
async updateMerchant(accountId: string, data: Partial<CreateConnectedAccountDto>) {
  return this.connectedAccountsService.updateConnectedAccount(accountId, data);
}

// Create a payout for a connected account
async createPayout(accountId: string, amount: number) {
  return this.connectedAccountsService.createPayout(accountId, amount, 'usd');
}

Webhook Handling for Connected Accounts

Create handlers for important Connect events:

@Injectable()
export class ConnectedAccountWebhookHandler {
  private readonly logger = new Logger(ConnectedAccountWebhookHandler.name);

  @StripeWebhookHandler('account.updated')
  async handleAccountUpdate(event: Stripe.Event): Promise<void> {
    const account = event.data.object as Stripe.Account;
    // Check if account is now fully onboarded
    if (account.charges_enabled && account.payouts_enabled) {
      // Update merchant status in your database
    }
  }

  @StripeWebhookHandler('account.application.deauthorized')
  async handleAccountDeauthorized(event: Stripe.Event): Promise<void> {
    const application = event.data.object as any;
    // Handle when a connected account removes your platform's access
  }
}

Contributing 🤝

Contributions are welcome! Please feel free to submit a Pull Request.

License 📄

MIT


Made with ❤️ by Reyco1