@xenterprises/fastify-xstripe 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  * TypeScript Type Definitions
4
4
  *
5
5
  * @module @xenterprises/fastify-xstripe
6
- * @version 1.0.0
6
+ * @version 1.2.0
7
7
  */
8
8
 
9
- import { FastifyPluginAsync, FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
9
+ import { FastifyPluginAsync, FastifyInstance } from 'fastify';
10
10
  import Stripe from 'stripe';
11
11
 
12
12
  /**
@@ -16,6 +16,10 @@ export type StripeWebhookEvent = Stripe.Event;
16
16
 
17
17
  /**
18
18
  * Webhook Event Handler Function
19
+ *
20
+ * @param event - The Stripe webhook event object
21
+ * @param fastify - The Fastify instance (access to decorators)
22
+ * @param stripe - The Stripe client instance
19
23
  */
20
24
  export type WebhookEventHandler = (
21
25
  event: StripeWebhookEvent,
@@ -72,94 +76,20 @@ export interface DefaultHandlers {
72
76
  * Plugin Configuration Options
73
77
  */
74
78
  export interface XStripePluginOptions {
75
- /** Stripe API Key (Secret Key) */
76
- apiKey?: string;
79
+ /** Stripe API Key (Secret Key). Required. */
80
+ apiKey: string;
77
81
 
78
- /** Stripe Webhook Signing Secret */
79
- webhookSecret?: string;
82
+ /** Stripe Webhook Signing Secret. Required. */
83
+ webhookSecret: string;
80
84
 
81
- /** Webhook endpoint path */
85
+ /** Webhook endpoint path. Defaults to "/stripe/webhook". */
82
86
  webhookPath?: string;
83
87
 
84
- /** Custom event handlers (override defaults) */
88
+ /** Custom event handlers (override defaults). */
85
89
  handlers?: Partial<DefaultHandlers>;
86
90
 
87
- /** Whether to fail on handler errors */
88
- failOnError?: boolean;
89
-
90
- /** Request timeout in milliseconds */
91
- requestTimeout?: number;
92
-
93
- /** Enable event logging */
94
- logEvents?: boolean;
95
- }
96
-
97
- /**
98
- * Helper Functions
99
- */
100
- export namespace helpers {
101
- /**
102
- * Format amount as currency string
103
- * @param amount Amount in cents
104
- * @param currency Currency code (USD, EUR, etc.)
105
- * @returns Formatted currency string (e.g., "$20.00")
106
- */
107
- function formatAmount(amount: number, currency: string): string;
108
-
109
- /**
110
- * Get plan name from subscription
111
- * @param subscription Subscription object
112
- * @returns Plan name or product name
113
- */
114
- function getPlanName(subscription: Stripe.Subscription): string;
115
-
116
- /**
117
- * Check if subscription is active
118
- * @param subscription Subscription object
119
- * @returns True if subscription is active
120
- */
121
- function isActiveSubscription(subscription: Stripe.Subscription): boolean;
122
-
123
- /**
124
- * Get customer email from various objects
125
- * @param obj Stripe object with customer reference
126
- * @param stripe Stripe client instance
127
- * @returns Customer email
128
- */
129
- function getCustomerEmail(obj: any, stripe: Stripe): Promise<string>;
130
-
131
- /**
132
- * Create idempotent request ID from event
133
- * @param event Stripe webhook event
134
- * @returns Idempotency key
135
- */
136
- function createIdempotencyKey(event: StripeWebhookEvent): string;
137
- }
138
-
139
- /**
140
- * xStripe Service Methods (available on fastify.xStripe)
141
- */
142
- export interface XStripeService {
143
- /**
144
- * Register custom event handler
145
- * @param eventType Stripe event type
146
- * @param handler Event handler function
147
- */
148
- onEvent(eventType: string, handler: WebhookEventHandler): void;
149
-
150
- /**
151
- * Get registered handler for event type
152
- * @param eventType Stripe event type
153
- * @returns Handler function or undefined
154
- */
155
- getHandler(eventType: string): WebhookEventHandler | undefined;
156
-
157
- /**
158
- * Execute event handler
159
- * @param event Stripe webhook event
160
- * @returns Promise that resolves when handler completes
161
- */
162
- executeHandler(event: StripeWebhookEvent): Promise<void>;
91
+ /** Stripe API version. Defaults to "2024-11-20.acacia". */
92
+ apiVersion?: string;
163
93
  }
164
94
 
165
95
  /**
@@ -167,101 +97,77 @@ export interface XStripeService {
167
97
  */
168
98
  declare module 'fastify' {
169
99
  interface FastifyInstance {
170
- /** xStripe service methods */
171
- xStripe: XStripeService;
172
-
173
100
  /** Stripe client instance */
174
101
  stripe: Stripe;
175
102
  }
176
103
  }
177
104
 
178
105
  /**
179
- * Webhook Request with Stripe Event
106
+ * Helper Functions
180
107
  */
181
- export interface WebhookRequest extends FastifyRequest {
182
- body: {
183
- /** Stripe event object */
184
- id: string;
185
- type: string;
186
- data: any;
187
- [key: string]: any;
188
- };
189
- }
108
+ export declare namespace helpers {
109
+ /** Extract customer email from various Stripe objects */
110
+ function getCustomerEmail(event: StripeWebhookEvent, stripe: Stripe): string | Promise<string | null> | null;
190
111
 
191
- /**
192
- * Webhook Response
193
- */
194
- export interface WebhookResponse {
195
- /** Status message */
196
- status: 'success' | 'error';
112
+ /** Format amount from cents to currency string */
113
+ function formatAmount(cents: number, currency?: string): string;
114
+
115
+ /** Get plan name from subscription */
116
+ function getPlanName(subscription: Stripe.Subscription): string;
197
117
 
198
- /** Event ID that was processed */
199
- eventId: string;
118
+ /** Check if subscription is in trial */
119
+ function isInTrial(subscription: Stripe.Subscription): boolean;
200
120
 
201
- /** Event type that was processed */
202
- eventType: string;
121
+ /** Check if subscription is active (including trialing) */
122
+ function isActiveSubscription(subscription: Stripe.Subscription): boolean;
203
123
 
204
- /** Optional error message */
205
- error?: string;
124
+ /** Get days until trial ends */
125
+ function getDaysUntilTrialEnd(subscription: Stripe.Subscription): number | null;
206
126
 
207
- /** Timestamp of processing */
208
- processedAt: string;
209
- }
127
+ /** Check if event is a subscription renewal */
128
+ function isRenewal(event: StripeWebhookEvent): boolean;
210
129
 
211
- /**
212
- * API Response Types
213
- */
214
- export interface Plan {
215
- productId: string;
216
- name: string;
217
- description?: string;
218
- images?: string[];
219
- prices: PriceInfo[];
220
- metadata?: Record<string, any>;
221
- }
130
+ /** Get subscription status display text */
131
+ function getSubscriptionStatusText(status: string): string;
222
132
 
223
- export interface PriceInfo {
224
- priceId: string;
225
- amount: number;
226
- currency: string;
227
- interval?: string;
228
- intervalCount?: number;
229
- trialPeriodDays?: number;
230
- nickname?: string;
231
- metadata?: Record<string, any>;
232
- }
133
+ /** Extract metadata from event */
134
+ function getMetadata(event: StripeWebhookEvent): Record<string, string>;
233
135
 
234
- export interface PaymentMethod {
235
- id: string;
236
- type: string;
237
- isDefault: boolean;
238
- card?: {
239
- brand: string;
240
- last4: string;
241
- expMonth: number;
242
- expYear: number;
243
- };
244
- billingDetails?: Record<string, any>;
245
- createdAt: Date;
246
- }
136
+ /** Check if event is a test event */
137
+ function isTestEvent(event: StripeWebhookEvent): boolean;
138
+
139
+ /** Get human-readable event description */
140
+ function getEventDescription(event: StripeWebhookEvent): string;
141
+
142
+ /** Calculate MRR from subscription (in cents) */
143
+ function calculateMRR(subscription: Stripe.Subscription): number;
144
+
145
+ /** Get payment method type display text */
146
+ function getPaymentMethodType(paymentMethod: Stripe.PaymentMethod | null): string;
247
147
 
248
- export interface SubscriptionInfo {
249
- id: string;
250
- status: string;
251
- planId?: string;
252
- planName?: string;
253
- amount?: number;
254
- currency?: string;
255
- interval?: string;
256
- createdAt: Date;
257
- currentPeriodEnd: Date;
258
- cancelAtPeriodEnd: boolean;
259
- canceledAt?: Date;
148
+ /** Extract line items from invoice */
149
+ function getInvoiceLineItems(invoice: Stripe.Invoice): Array<{
150
+ description: string | null;
151
+ amount: number;
152
+ quantity: number | null;
153
+ priceId: string | undefined;
154
+ }>;
155
+
156
+ /** Check if invoice is for subscription vs one-time payment */
157
+ function isSubscriptionInvoice(invoice: Stripe.Invoice): boolean;
158
+
159
+ /** Get next billing date from subscription */
160
+ function getNextBillingDate(subscription: Stripe.Subscription): Date | null;
161
+
162
+ /** Format date from Unix timestamp */
163
+ function formatDate(unixTimestamp: number | null, locale?: string): string | null;
260
164
  }
261
165
 
262
166
  /**
263
167
  * xStripe Plugin
264
- * Registers Stripe webhook handling and provides convenient API endpoints
168
+ *
169
+ * Registers Stripe webhook handling with signature verification and
170
+ * decorates the Fastify instance with a Stripe client.
265
171
  *
266
172
  * @example
267
173
  * ```typescript
@@ -273,37 +179,25 @@ export interface SubscriptionInfo {
273
179
  * await fastify.register(xStripe, {
274
180
  * apiKey: process.env.STRIPE_API_KEY,
275
181
  * webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
276
- * webhookPath: '/webhooks/stripe'
277
- * });
278
- *
279
- * // Register custom handler
280
- * fastify.xStripe.onEvent('customer.subscription.created', async (event, fastify, stripe) => {
281
- * const subscription = event.data.object;
282
- * console.log('New subscription:', subscription.id);
182
+ * webhookPath: '/stripe/webhook',
183
+ * handlers: {
184
+ * 'customer.subscription.created': async (event, fastify, stripe) => {
185
+ * const subscription = event.data.object;
186
+ * console.log('New subscription:', subscription.id);
187
+ * },
188
+ * },
283
189
  * });
284
- *
285
- * // Webhook route automatically registered at webhookPath
286
- * // POST /webhooks/stripe
287
- *
288
- * // Use included API endpoints
289
- * // GET /plans
290
- * // GET /plans/:productId
291
- * // POST /create-checkout-session
292
- * // POST /create-payment-session
293
- * // GET /customer/:customerId/subscriptions
294
- * // POST /subscription/:id/update
295
- * // GET /customer/:customerId/payment-methods
296
- * // POST /customer/:customerId/payment-methods
297
- * // POST /customer/:customerId/payment-methods/:paymentMethodId/default
298
- * // DELETE /customer/:customerId/payment-methods/:paymentMethodId
299
190
  * ```
300
191
  */
301
192
  declare const xStripe: FastifyPluginAsync<XStripePluginOptions>;
302
193
 
303
194
  export default xStripe;
195
+ export { xStripe };
304
196
 
305
- /**
306
- * Re-export Stripe types for convenience
307
- */
197
+ /** Re-export for convenience */
308
198
  export { Stripe };
309
199
  export type { StripeWebhookEvent as WebhookEvent };
200
+
201
+ /** Default handlers */
202
+ export { defaultHandlers } from './src/handlers/defaultHandlers.js';
203
+ export { exampleHandlers } from './src/handlers/exampleHandlers.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xenterprises/fastify-xstripe",
3
3
  "type": "module",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "description": "Fastify plugin for Stripe webhooks with simplified, testable handlers for subscription events.",
6
6
  "main": "src/index.js",
7
7
  "exports": {
@@ -12,10 +12,10 @@
12
12
  "scripts": {
13
13
  "start": "fastify start -l info server/app.js",
14
14
  "dev": "fastify start -w -l info -P server/app.js",
15
- "test": "node --test test/handlers.test.js"
15
+ "test": "node --test test/handlers.test.js test/xStripe.integration.test.js"
16
16
  },
17
17
  "author": "Tim Mushen",
18
- "license": "ISC",
18
+ "license": "UNLICENSED",
19
19
  "engines": {
20
20
  "node": ">=20.0.0",
21
21
  "npm": ">=10.0.0"
@@ -40,7 +40,6 @@
40
40
  "devDependencies": {
41
41
  "@types/node": "^22.7.4",
42
42
  "fastify": "^5.1.0",
43
- "fastify-plugin": "^5.0.0",
44
43
  "typescript": "^5.6.3"
45
44
  },
46
45
  "dependencies": {
@@ -20,8 +20,8 @@ export async function setupWebhooks(fastify, options) {
20
20
  const sig = request.headers["stripe-signature"];
21
21
 
22
22
  if (!sig) {
23
- fastify.log.error("Missing stripe-signature header");
24
- return reply.code(400).send({ error: "Missing stripe-signature header" });
23
+ fastify.log.error("[xStripe] Missing stripe-signature header");
24
+ return reply.code(400).send({ error: "[xStripe] Missing stripe-signature header" });
25
25
  }
26
26
 
27
27
  let event;
@@ -33,12 +33,12 @@ export async function setupWebhooks(fastify, options) {
33
33
  // Verify webhook signature
34
34
  event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret);
35
35
  } catch (err) {
36
- fastify.log.error(`Webhook signature verification failed: ${err.message}`);
37
- return reply.code(400).send({ error: `Webhook Error: ${err.message}` });
36
+ fastify.log.error(`[xStripe] Webhook signature verification failed: ${err.message}`);
37
+ return reply.code(400).send({ error: `[xStripe] Webhook signature verification failed: ${err.message}` });
38
38
  }
39
39
 
40
40
  // Log the event
41
- fastify.log.info(`Received Stripe webhook: ${event.type}`);
41
+ fastify.log.info(`[xStripe] Received webhook: ${event.type}`);
42
42
 
43
43
  // Get handler for this event type
44
44
  const handler = eventHandlers[event.type];
@@ -47,9 +47,9 @@ export async function setupWebhooks(fastify, options) {
47
47
  try {
48
48
  // Execute handler
49
49
  await handler(event, fastify, stripe);
50
- fastify.log.info(`Successfully processed ${event.type}`);
50
+ fastify.log.info(`[xStripe] Successfully processed ${event.type}`);
51
51
  } catch (err) {
52
- fastify.log.error(`Error processing ${event.type}: ${err.message}`);
52
+ fastify.log.error(`[xStripe] Error processing ${event.type}: ${err.message}`);
53
53
  // Return 200 to acknowledge receipt, even if processing failed
54
54
  // This prevents Stripe from retrying immediately
55
55
  return reply.code(200).send({
@@ -59,7 +59,7 @@ export async function setupWebhooks(fastify, options) {
59
59
  });
60
60
  }
61
61
  } else {
62
- fastify.log.warn(`No handler registered for event type: ${event.type}`);
62
+ fastify.log.warn(`[xStripe] No handler registered for event type: ${event.type}`);
63
63
  }
64
64
 
65
65
  // Always return 200 to acknowledge receipt
@@ -67,6 +67,6 @@ export async function setupWebhooks(fastify, options) {
67
67
  }
68
68
  );
69
69
 
70
- console.info(` Stripe Webhooks Enabled at ${webhookPath}`);
71
- console.info(` 📋 Registered ${Object.keys(eventHandlers).length} event handlers`);
70
+ fastify.log.info(`[xStripe] Webhooks enabled at ${webhookPath}`);
71
+ fastify.log.info(`[xStripe] Registered ${Object.keys(eventHandlers).length} event handlers`);
72
72
  }
package/src/xStripe.js CHANGED
@@ -13,11 +13,27 @@ async function xStripe(fastify, options) {
13
13
  } = options;
14
14
 
15
15
  // Validate required options
16
- if (!apiKey) {
17
- throw new Error("Stripe apiKey is required");
16
+ if (!apiKey || typeof apiKey !== "string") {
17
+ throw new Error("[xStripe] apiKey is required and must be a string");
18
18
  }
19
19
 
20
- console.info("\n 💳 Starting xStripe...\n");
20
+ if (!webhookSecret || typeof webhookSecret !== "string") {
21
+ throw new Error("[xStripe] webhookSecret is required and must be a string");
22
+ }
23
+
24
+ if (typeof webhookPath !== "string") {
25
+ throw new Error("[xStripe] webhookPath must be a string");
26
+ }
27
+
28
+ if (typeof handlers !== "object" || Array.isArray(handlers)) {
29
+ throw new Error("[xStripe] handlers must be a plain object");
30
+ }
31
+
32
+ if (typeof apiVersion !== "string") {
33
+ throw new Error("[xStripe] apiVersion must be a string");
34
+ }
35
+
36
+ fastify.log.info("[xStripe] Initializing Stripe plugin...");
21
37
 
22
38
  // Initialize Stripe client
23
39
  const stripe = new Stripe(apiKey, { apiVersion });
@@ -25,21 +41,18 @@ async function xStripe(fastify, options) {
25
41
  // Decorate Fastify with Stripe client
26
42
  fastify.decorate("stripe", stripe);
27
43
 
28
- // Setup webhook handling if webhook secret is provided
29
- if (webhookSecret) {
30
- await setupWebhooks(fastify, {
31
- stripe,
32
- webhookSecret,
33
- webhookPath,
34
- handlers,
35
- });
36
- } else {
37
- fastify.log.warn("⚠️ Stripe webhook secret not provided. Webhook handling disabled.");
38
- }
44
+ // Setup webhook handling
45
+ await setupWebhooks(fastify, {
46
+ stripe,
47
+ webhookSecret,
48
+ webhookPath,
49
+ handlers,
50
+ });
39
51
 
40
- console.info("\n 💳 xStripe Ready!\n");
52
+ fastify.log.info("[xStripe] Ready");
41
53
  }
42
54
 
43
55
  export default fp(xStripe, {
44
56
  name: "xStripe",
57
+ fastify: ">=5.0.0",
45
58
  });
package/.dockerignore DELETED
@@ -1,62 +0,0 @@
1
- # Git
2
- .git
3
- .gitignore
4
- .gitattributes
5
-
6
- # Node modules (will be installed in container)
7
- node_modules
8
- npm-debug.log*
9
- yarn-debug.log*
10
- yarn-error.log*
11
- lerna-debug.log*
12
-
13
- # Dependencies
14
- .npm
15
- .eslintcache
16
- *.tsbuildinfo
17
-
18
- # Temporary files
19
- .DS_Store
20
- Thumbs.db
21
- *.tmp
22
- .env.local
23
- .env.*.local
24
-
25
- # IDE
26
- .vscode
27
- .idea
28
- *.swp
29
- *.swo
30
- *.swn
31
- *~
32
-
33
- # Test & Coverage
34
- coverage
35
- .nyc_output
36
- test
37
- *.test.js
38
-
39
- # Build artifacts (if any)
40
- dist
41
- build
42
- out
43
-
44
- # Docker files (not needed in image)
45
- Dockerfile*
46
- docker-compose*.yml
47
- .dockerignore
48
-
49
- # CI/CD
50
- .github
51
- .gitlab-ci.yml
52
-
53
- # Documentation (optional - include if you want docs in image)
54
- # *.md
55
-
56
- # Example/demo files
57
- examples
58
- demo
59
-
60
- # Logs
61
- logs
62
- *.log
package/.env.example DELETED
@@ -1,116 +0,0 @@
1
- # ============================================================================
2
- # xStripe Fastify Plugin - Environment Configuration
3
- # ============================================================================
4
- # This file defines environment variables for the xStripe module.
5
- # Copy this to .env and update values for your environment.
6
- # ============================================================================
7
-
8
- # ============================================================================
9
- # SERVER CONFIGURATION
10
- # ============================================================================
11
-
12
- # Server port (default: 3000)
13
- PORT=3000
14
-
15
- # Node environment (development | production | test)
16
- NODE_ENV=development
17
-
18
- # Server hostname/domain (used in webhook URLs and responses)
19
- # Update this to your actual domain in production
20
- DOMAIN=http://localhost:3000
21
-
22
- # ============================================================================
23
- # STRIPE CONFIGURATION - REQUIRED
24
- # ============================================================================
25
-
26
- # Stripe API Key (Secret Key)
27
- # Get from: https://dashboard.stripe.com/apikeys
28
- # Format: sk_test_... (test mode) or sk_live_... (production)
29
- # NEVER commit this value - use environment variables only
30
- STRIPE_API_KEY=sk_test_your_key_here
31
-
32
- # Stripe Webhook Signing Secret
33
- # Get from: https://dashboard.stripe.com/webhooks
34
- # Copy the "Signing secret" for your webhook endpoint
35
- # Format: whsec_...
36
- STRIPE_WEBHOOK_SECRET=whsec_your_secret_here
37
-
38
- # ============================================================================
39
- # WEBHOOK CONFIGURATION
40
- # ============================================================================
41
-
42
- # Webhook endpoint path (relative to domain)
43
- # Default: /webhooks/stripe
44
- # STRIPE_WEBHOOK_PATH=/webhooks/stripe
45
-
46
- # Webhook events to listen for (space or comma-separated)
47
- # Default: all supported events
48
- # Examples:
49
- # customer.created,customer.updated,customer.deleted
50
- # invoice.created,invoice.finalized,invoice.paid
51
- # charge.succeeded,charge.failed
52
- # STRIPE_WEBHOOK_EVENTS=*
53
-
54
- # ============================================================================
55
- # LOGGING & MONITORING
56
- # ============================================================================
57
-
58
- # Fastify logger level (trace, debug, info, warn, error, fatal)
59
- LOG_LEVEL=info
60
-
61
- # Enable webhook event logging
62
- # LOG_WEBHOOK_EVENTS=true
63
-
64
- # Enable handler execution logging
65
- # LOG_HANDLER_EXECUTION=true
66
-
67
- # ============================================================================
68
- # ERROR HANDLING & RECOVERY
69
- # ============================================================================
70
-
71
- # Retry failed webhook handler executions
72
- # ENABLE_HANDLER_RETRY=false
73
-
74
- # Max retry attempts for failed handlers
75
- # HANDLER_RETRY_MAX_ATTEMPTS=3
76
-
77
- # Retry delay in milliseconds
78
- # HANDLER_RETRY_DELAY_MS=5000
79
-
80
- # ============================================================================
81
- # SECURITY
82
- # ============================================================================
83
-
84
- # Enable HTTPS enforcement (if behind reverse proxy)
85
- # HTTPS_ONLY=false
86
-
87
- # Rate limiting: max webhook requests per minute
88
- # WEBHOOK_RATE_LIMIT_WINDOW=1m
89
- # WEBHOOK_RATE_LIMIT_MAX=1000
90
-
91
- # Timeout for webhook handler execution (milliseconds)
92
- # Default: 30000 (30 seconds)
93
- # WEBHOOK_HANDLER_TIMEOUT=30000
94
-
95
- # ============================================================================
96
- # STRIPE ACCOUNT CONFIGURATION
97
- # ============================================================================
98
-
99
- # Stripe API Version (optional - uses account default if not set)
100
- # Format: YYYY-MM-DD or leave empty
101
- # STRIPE_API_VERSION=
102
-
103
- # Enable Stripe CLI webhooks in development
104
- # Set to true if using: stripe listen --forward-to localhost:3000/webhooks/stripe
105
- # STRIPE_CLI_MODE=false
106
-
107
- # ============================================================================
108
- # DATABASE/STORAGE (optional - for persisting event data)
109
- # ============================================================================
110
-
111
- # Database URL for event persistence (optional)
112
- # Example: postgresql://user:pass@localhost/stripe_events
113
- # DATABASE_URL=
114
-
115
- # Enable event persistence
116
- # PERSIST_WEBHOOK_EVENTS=false