Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 63 additions & 10 deletions docs/modules/stripe.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,25 @@ pnpm add @golevelup/nestjs-stripe stripe

- 🔒 Automatically validates that the event payload was actually sent from Stripe using the configured webhook signing secret

- 🕵️ Discovers providers from your application decorated with `StripeWebhookHandler` and routes incoming events to them
- 🕵️ Discovers providers from your application decorated with `StripeWebhookHandler` and `StripeThinWebhookHandler` and routes incoming events to them

- 🧭 Route events to logical services easily simply by providing the Stripe webhook event type

- ⚡ Support for both traditional snapshot events and Stripe's new v2 thin events

## Import

Import and add `StripeModule` to the `imports` section of the consuming module (most likely `AppModule`). Your Stripe API key is required, and you can optionally include a webhook configuration if you plan on consuming Stripe webhook events inside your app.
Stripe secrets you can get from your Dashboards [Webhooks settings](https://dashboard.stripe.com/webhooks). Select an endpoint that you want to obtain the secret for, then click the Reveal link below "Signing secret".
Import and add `StripeModule` to the `imports` section of the consuming module (most likely `AppModule`). Your Stripe API key is required, and you can optionally include a webhook configuration if you plan on consuming Stripe webhook events inside your app.
Stripe secrets you can get from your Dashboard's [Webhooks settings](https://dashboard.stripe.com/webhooks). Select an endpoint that you want to obtain the secret for, then click the Reveal link below "Signing secret".

`account` - The webhook secret registered in the Stripe Dashboard for events on your accounts
`account_test` - The webhook secret registered in the Stripe Dashboard for events on your accounts in test mode
**Snapshot Events (Traditional Webhooks):**
`account` - The webhook secret registered in the Stripe Dashboard for events on your accounts
`accountTest` - The webhook secret registered in the Stripe Dashboard for events on your accounts in test mode
`connect` - The webhook secret registered in the Stripe Dashboard for events on Connected accounts
`connect_test` - The webhook secret registered in the Stripe Dashboard for events on Connected accounts in test mode
`connectTest` - The webhook secret registered in the Stripe Dashboard for events on Connected accounts in test mode

**Thin Events (V2 Webhooks - Optional):**
`stripeThinSecrets` - Separate secrets for Stripe's v2 thin events (only required if using `@StripeThinWebhookHandler`)

```typescript
import { StripeModule } from '@golevelup/nestjs-stripe';
Expand All @@ -56,12 +62,20 @@ import { StripeModule } from '@golevelup/nestjs-stripe';
StripeModule.forRoot({
apiKey: 'sk_***',
webhookConfig: {
// Snapshot event secrets
stripeSecrets: {
account: 'whsec_***',
accountTest: 'whsec_***',
connect: 'whsec_***',
connectTest: 'whsec_***',
},
// Thin event secrets (optional)
stripeThinSecrets: {
account: 'whsec_***',
accountTest: 'whsec_***',
connect: 'whsec_***',
connectTest: 'whsec_***',
},
},
}),
],
Expand Down Expand Up @@ -129,24 +143,63 @@ Exposing provider/service methods to be used for processing Stripe events is eas

[Review the Stripe documentation](https://stripe.com/docs/api/events/types) for more information about the types of events available.

#### Snapshot Events (Traditional)

Use `@StripeWebhookHandler` for traditional Stripe webhook events. These include the full event object in the payload.

```typescript
@Injectable()
class PaymentCreatedService {
@StripeWebhookHandler('payment_intent.created')
handlePaymentIntentCreated(evt: Stripe.PaymentIntentPaymentCreatedEvent) {
// execute your custom business logic
}
}
```

**Webhook URL:** `https://your-domain.com/stripe/webhook`

#### Thin Events (V2)

Use `@StripeThinWebhookHandler` for Stripe's v2 thin events. These are lightweight events that only include metadata - you'll need to fetch the full object separately.

@StripeWebhookHandler('v1.billing.meter.no_meter_found')
async handleBillingMeterNoMeterFound(
nft: Stripe.Events.V1BillingMeterNoMeterFoundEventNotification,
```typescript
@Injectable()
class BillingService {
constructor(@InjectStripeClient() private stripe: Stripe) {}

@StripeThinWebhookHandler('v1.billing.meter.error_report_triggered')
async handleBillingMeterError(
evt: Stripe.Events.V1BillingMeterErrorReportTriggeredEventNotification,
) {
const event = await nft.fetchEvent();
// Thin events require fetching the full object
const meter = await evt.fetchRelatedObject();
// execute your custom business logic
}
}
```

**Webhook URL:** `https://your-domain.com/stripe/webhook?mode=thin`

#### Wildcard Handlers

You can use `'*'` to handle all events of a specific type:

```typescript
@Injectable()
class WebhookLogger {
@StripeWebhookHandler('*')
logAllSnapshotEvents(evt: Stripe.Event) {
console.log('Received snapshot event:', evt.type);
}

@StripeThinWebhookHandler('*')
logAllThinEvents(evt: Stripe.V2.Core.Event) {
console.log('Received thin event:', evt.type);
}
}
```

### Webhook Controller Decorators

You can also pass any class decorator to the `decorators` property of the `webhookConfig` object as a part of the module configuration. This could be used in situations like when using the `@nestjs/throttler` package and needing to apply the `@SkipThrottle()` decorator, or when you have a global guard but need to skip routes with certain metadata.
Expand Down
4 changes: 4 additions & 0 deletions packages/stripe/src/stripe.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export const STRIPE_CLIENT_TOKEN = Symbol('STRIPE_CLIENT_TOKEN');

export const STRIPE_WEBHOOK_HANDLER = Symbol('STRIPE_WEBHOOK_HANDLER');

export const STRIPE_THIN_WEBHOOK_HANDLER = Symbol(
'STRIPE_THIN_WEBHOOK_HANDLER',
);

export const STRIPE_WEBHOOK_SERVICE = Symbol('STRIPE_WEBHOOK_SERVICE');

export const STRIPE_WEBHOOK_CONTEXT_TYPE = 'stripe_webhook';
13 changes: 12 additions & 1 deletion packages/stripe/src/stripe.decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MODULE_OPTIONS_TOKEN } from './stripe-module-definition';
import {
STRIPE_CLIENT_TOKEN,
STRIPE_WEBHOOK_HANDLER,
STRIPE_THIN_WEBHOOK_HANDLER,
} from './stripe.constants';

/**
Expand All @@ -20,10 +21,20 @@ export const InjectStripeClient = () => Inject(STRIPE_CLIENT_TOKEN);
* Binds the decorated service method as a handler for incoming Stripe Webhook events.
* Events will be automatically routed here based on their event type property
*
* @param eventType The Stripe event type to bind the handler to (either normal or thin events)
* @param eventType The Stripe event type to bind the handler to
*/
export const StripeWebhookHandler = (
eventType:
| Stripe.WebhookEndpointCreateParams.EnabledEvent
| Stripe.V2.Core.Event['type'],
) => SetMetadata(STRIPE_WEBHOOK_HANDLER, eventType);

/**
* Binds the decorated service method as a handler for incoming Stripe Thin Webhook events.
* Events will be automatically routed here based on their event type property
*
* @param eventType The Stripe thin event type to bind the handler to, or '*' for all events
*/
export const StripeThinWebhookHandler = (
eventType: Stripe.V2.Core.Event['type'] | '*',
) => SetMetadata(STRIPE_THIN_WEBHOOK_HANDLER, eventType);
18 changes: 17 additions & 1 deletion packages/stripe/src/stripe.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];

interface StripeSecrets {
/**
* The mode of Stripe webhook being used
*/
export enum StripeWebhookMode {
SNAPSHOT = 'snapshot',
THIN = 'thin',
}

export interface StripeSecrets {
/**
* The webhook secret registered in the Stripe Dashboard for events on your accounts
*/
Expand All @@ -33,8 +41,16 @@ export interface StripeModuleConfig extends Partial<Stripe.StripeConfig> {
* Configuration for processing Stripe Webhooks
*/
webhookConfig?: {
/**
* Secrets for validating incoming webhook **snapshot** signatures. At least one secret must be provided.
*/
stripeSecrets: RequireAtLeastOne<StripeSecrets>;

/**
* Secrets for validating incoming webhook **thin** signatures. At least one secret must be provided if using thin webhooks.
*/
stripeThinSecrets?: RequireAtLeastOne<StripeSecrets>;

/**
* The property on the request that contains the raw message body so that it
* can be validated. Defaults to 'body'
Expand Down
Loading