Skip to content

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

License

Notifications You must be signed in to change notification settings

reyco1/nestjs-stripe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@reyco1/nestjs-stripe

Nest Logo Stripe Logo

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 🎣

@Controller('stripe/webhooks')
export class StripeWebhookController {
  constructor(
    private readonly stripeService: StripeService,
    private readonly stripeUtils: StripeUtils
  ) {}

  @Post()
  async handleWebhook(
    @Headers('stripe-signature') signature: string,
    @Req() request: Request
  ) {
    const event = await this.stripeService.createWebhookEvent(
      request.body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET
    );

    switch (event.type) {
      case 'payment_intent.succeeded': {
        const paymentIntent = event.data.object as Stripe.PaymentIntent;
        const [customer, paymentMethod] = await Promise.all([
          this.stripeUtils.getCustomerDetails(paymentIntent),
          this.stripeUtils.getPaymentMethodDetails(paymentIntent)
        ]);
        // Handle successful payment
        break;
      }
      
      case 'customer.subscription.created': {
        const subscription = event.data.object as Stripe.Subscription;
        const details = await this.stripeUtils.getSubscriptionDetails(subscription);
        // Handle new subscription
        break;
      }
    }

    return { received: true };
  }
}

Contributing 🀝

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

License πŸ“„

MIT


Made with ❀️ by Reyco1

About

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published