@digilogiclabs/saas-factory-payments 0.1.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.
@@ -0,0 +1,443 @@
1
+ // src/server/api/stripe-server.ts
2
+ import Stripe from "stripe";
3
+
4
+ // src/shared/constants.ts
5
+ var STRIPE_API_VERSION = "2023-10-16";
6
+ var WEBHOOK_EVENTS = {
7
+ CUSTOMER_SUBSCRIPTION_CREATED: "customer.subscription.created",
8
+ CUSTOMER_SUBSCRIPTION_UPDATED: "customer.subscription.updated",
9
+ CUSTOMER_SUBSCRIPTION_DELETED: "customer.subscription.deleted",
10
+ INVOICE_PAYMENT_SUCCEEDED: "invoice.payment_succeeded",
11
+ INVOICE_PAYMENT_FAILED: "invoice.payment_failed",
12
+ CHECKOUT_SESSION_COMPLETED: "checkout.session.completed",
13
+ PAYMENT_INTENT_SUCCEEDED: "payment_intent.succeeded",
14
+ PAYMENT_INTENT_PAYMENT_FAILED: "payment_intent.payment_failed"
15
+ };
16
+
17
+ // src/server/api/stripe-server.ts
18
+ var StripeServerAPI = class {
19
+ constructor(secretKey, options) {
20
+ this.stripe = new Stripe(secretKey, {
21
+ apiVersion: options?.apiVersion || STRIPE_API_VERSION,
22
+ typescript: true
23
+ });
24
+ }
25
+ // Customer methods
26
+ async createCustomer(params) {
27
+ return this.stripe.customers.create({
28
+ email: params.email,
29
+ name: params.name,
30
+ phone: params.phone,
31
+ metadata: params.metadata
32
+ });
33
+ }
34
+ async getCustomer(customerId) {
35
+ return this.stripe.customers.retrieve(customerId);
36
+ }
37
+ async updateCustomer(customerId, params) {
38
+ return this.stripe.customers.update(customerId, params);
39
+ }
40
+ async deleteCustomer(customerId) {
41
+ return this.stripe.customers.del(customerId);
42
+ }
43
+ // Subscription methods
44
+ async createSubscription(params) {
45
+ return this.stripe.subscriptions.create({
46
+ customer: params.customerId,
47
+ items: [
48
+ {
49
+ price: params.priceId,
50
+ quantity: params.quantity || 1
51
+ }
52
+ ],
53
+ trial_period_days: params.trialPeriodDays,
54
+ metadata: params.metadata,
55
+ payment_behavior: params.paymentBehavior || "default_incomplete",
56
+ payment_settings: {
57
+ save_default_payment_method: "on_subscription"
58
+ },
59
+ expand: ["latest_invoice.payment_intent"]
60
+ });
61
+ }
62
+ async getSubscription(subscriptionId) {
63
+ return this.stripe.subscriptions.retrieve(subscriptionId, {
64
+ expand: ["default_payment_method"]
65
+ });
66
+ }
67
+ async updateSubscription(subscriptionId, params) {
68
+ const updateData = {};
69
+ if (params.priceId) {
70
+ const subscription = await this.getSubscription(subscriptionId);
71
+ const currentItem = subscription.items.data[0];
72
+ updateData.items = [
73
+ {
74
+ id: currentItem.id,
75
+ price: params.priceId,
76
+ quantity: params.quantity || currentItem.quantity
77
+ }
78
+ ];
79
+ }
80
+ if (params.metadata) {
81
+ updateData.metadata = params.metadata;
82
+ }
83
+ if (typeof params.cancelAtPeriodEnd === "boolean") {
84
+ updateData.cancel_at_period_end = params.cancelAtPeriodEnd;
85
+ }
86
+ return this.stripe.subscriptions.update(subscriptionId, updateData);
87
+ }
88
+ async cancelSubscription(subscriptionId, immediately = false) {
89
+ if (immediately) {
90
+ return this.stripe.subscriptions.cancel(subscriptionId);
91
+ } else {
92
+ return this.stripe.subscriptions.update(subscriptionId, {
93
+ cancel_at_period_end: true
94
+ });
95
+ }
96
+ }
97
+ async reactivateSubscription(subscriptionId) {
98
+ return this.stripe.subscriptions.update(subscriptionId, {
99
+ cancel_at_period_end: false
100
+ });
101
+ }
102
+ async getCustomerSubscriptions(customerId) {
103
+ return this.stripe.subscriptions.list({
104
+ customer: customerId,
105
+ expand: ["data.default_payment_method"]
106
+ });
107
+ }
108
+ // Checkout methods
109
+ async createCheckoutSession(params) {
110
+ const sessionParams = {
111
+ payment_method_types: ["card"],
112
+ line_items: [
113
+ {
114
+ price: params.priceId,
115
+ quantity: 1
116
+ }
117
+ ],
118
+ mode: params.mode || "subscription",
119
+ success_url: params.successUrl,
120
+ cancel_url: params.cancelUrl,
121
+ allow_promotion_codes: params.allowPromotionCodes,
122
+ billing_address_collection: params.billingAddressCollection,
123
+ metadata: params.metadata
124
+ };
125
+ if (params.customerId) {
126
+ sessionParams.customer = params.customerId;
127
+ } else if (params.customerEmail) {
128
+ sessionParams.customer_email = params.customerEmail;
129
+ }
130
+ if (params.mode === "subscription" && params.trialPeriodDays) {
131
+ sessionParams.subscription_data = {
132
+ trial_period_days: params.trialPeriodDays
133
+ };
134
+ }
135
+ return this.stripe.checkout.sessions.create(sessionParams);
136
+ }
137
+ // Payment Intent methods
138
+ async createPaymentIntent(params) {
139
+ return this.stripe.paymentIntents.create({
140
+ amount: params.amount,
141
+ currency: params.currency,
142
+ customer: params.customerId,
143
+ payment_method_types: params.paymentMethodTypes || ["card"],
144
+ metadata: params.metadata,
145
+ description: params.description
146
+ });
147
+ }
148
+ async confirmPaymentIntent(paymentIntentId, params) {
149
+ return this.stripe.paymentIntents.confirm(paymentIntentId, {
150
+ payment_method: params?.paymentMethod
151
+ });
152
+ }
153
+ // Setup Intent methods (for subscriptions without immediate payment)
154
+ async createSetupIntent(params) {
155
+ return this.stripe.setupIntents.create({
156
+ customer: params.customerId,
157
+ payment_method_types: params.paymentMethodTypes || ["card"],
158
+ metadata: params.metadata,
159
+ usage: "off_session"
160
+ });
161
+ }
162
+ // Customer Portal methods
163
+ async createPortalSession(params) {
164
+ return this.stripe.billingPortal.sessions.create({
165
+ customer: params.customerId,
166
+ return_url: params.returnUrl
167
+ });
168
+ }
169
+ // Payment Method methods
170
+ async getCustomerPaymentMethods(customerId, type = "card") {
171
+ return this.stripe.paymentMethods.list({
172
+ customer: customerId,
173
+ type
174
+ });
175
+ }
176
+ async attachPaymentMethod(paymentMethodId, customerId) {
177
+ return this.stripe.paymentMethods.attach(paymentMethodId, {
178
+ customer: customerId
179
+ });
180
+ }
181
+ async detachPaymentMethod(paymentMethodId) {
182
+ return this.stripe.paymentMethods.detach(paymentMethodId);
183
+ }
184
+ async setDefaultPaymentMethod(customerId, paymentMethodId) {
185
+ return this.stripe.customers.update(customerId, {
186
+ invoice_settings: {
187
+ default_payment_method: paymentMethodId
188
+ }
189
+ });
190
+ }
191
+ // Invoice methods
192
+ async getCustomerInvoices(customerId, limit = 10) {
193
+ return this.stripe.invoices.list({
194
+ customer: customerId,
195
+ limit
196
+ });
197
+ }
198
+ async getInvoice(invoiceId) {
199
+ return this.stripe.invoices.retrieve(invoiceId);
200
+ }
201
+ // Webhook methods
202
+ constructWebhookEvent(payload, signature, webhookSecret) {
203
+ return this.stripe.webhooks.constructEvent(
204
+ payload,
205
+ signature,
206
+ webhookSecret
207
+ );
208
+ }
209
+ // Price and Product methods
210
+ async getPrice(priceId) {
211
+ return this.stripe.prices.retrieve(priceId, {
212
+ expand: ["product"]
213
+ });
214
+ }
215
+ async getPrices(params) {
216
+ return this.stripe.prices.list({
217
+ active: params?.active,
218
+ limit: params?.limit || 10,
219
+ product: params?.product,
220
+ expand: ["data.product"]
221
+ });
222
+ }
223
+ async getProduct(productId) {
224
+ return this.stripe.products.retrieve(productId);
225
+ }
226
+ // Ephemeral Key methods (for mobile)
227
+ async createEphemeralKey(customerId, apiVersion) {
228
+ return this.stripe.ephemeralKeys.create(
229
+ { customer: customerId },
230
+ { apiVersion }
231
+ );
232
+ }
233
+ };
234
+
235
+ // src/server/webhooks/stripe.ts
236
+ var StripeWebhookHandler = class {
237
+ constructor(secretKey, handlers = {}) {
238
+ this.stripeAPI = new StripeServerAPI(secretKey);
239
+ this.handlers = handlers;
240
+ }
241
+ async handleWebhook(payload, signature, webhookSecret) {
242
+ try {
243
+ const event = this.stripeAPI.constructWebhookEvent(
244
+ payload,
245
+ signature,
246
+ webhookSecret
247
+ );
248
+ console.log(`Received webhook event: ${event.type}`);
249
+ await this.processEvent(event);
250
+ return { success: true };
251
+ } catch (error) {
252
+ const errorMessage = error instanceof Error ? error.message : "Unknown webhook error";
253
+ console.error("Webhook error:", errorMessage);
254
+ return { success: false, error: errorMessage };
255
+ }
256
+ }
257
+ async processEvent(event) {
258
+ switch (event.type) {
259
+ case WEBHOOK_EVENTS.CUSTOMER_SUBSCRIPTION_CREATED:
260
+ await this.handleSubscriptionCreated(
261
+ event.data.object
262
+ );
263
+ break;
264
+ case WEBHOOK_EVENTS.CUSTOMER_SUBSCRIPTION_UPDATED:
265
+ await this.handleSubscriptionUpdated(
266
+ event.data.object
267
+ );
268
+ break;
269
+ case WEBHOOK_EVENTS.CUSTOMER_SUBSCRIPTION_DELETED:
270
+ await this.handleSubscriptionDeleted(
271
+ event.data.object
272
+ );
273
+ break;
274
+ case WEBHOOK_EVENTS.INVOICE_PAYMENT_SUCCEEDED:
275
+ await this.handleInvoicePaymentSucceeded(
276
+ event.data.object
277
+ );
278
+ break;
279
+ case WEBHOOK_EVENTS.INVOICE_PAYMENT_FAILED:
280
+ await this.handleInvoicePaymentFailed(
281
+ event.data.object
282
+ );
283
+ break;
284
+ case WEBHOOK_EVENTS.CHECKOUT_SESSION_COMPLETED:
285
+ await this.handleCheckoutSessionCompleted(
286
+ event.data.object
287
+ );
288
+ break;
289
+ case WEBHOOK_EVENTS.PAYMENT_INTENT_SUCCEEDED:
290
+ await this.handlePaymentIntentSucceeded(
291
+ event.data.object
292
+ );
293
+ break;
294
+ case WEBHOOK_EVENTS.PAYMENT_INTENT_PAYMENT_FAILED:
295
+ await this.handlePaymentIntentFailed(
296
+ event.data.object
297
+ );
298
+ break;
299
+ default:
300
+ console.log(`Unhandled event type: ${event.type}`);
301
+ }
302
+ }
303
+ async handleSubscriptionCreated(subscription) {
304
+ console.log(`Subscription created: ${subscription.id}`);
305
+ if (this.handlers.onCustomerSubscriptionCreated) {
306
+ await this.handlers.onCustomerSubscriptionCreated(subscription);
307
+ }
308
+ console.log(
309
+ `Customer ${subscription.customer} subscribed to ${subscription.items.data[0]?.price.id}`
310
+ );
311
+ }
312
+ async handleSubscriptionUpdated(subscription) {
313
+ console.log(`Subscription updated: ${subscription.id}`);
314
+ if (this.handlers.onCustomerSubscriptionUpdated) {
315
+ await this.handlers.onCustomerSubscriptionUpdated(subscription);
316
+ }
317
+ console.log(
318
+ `Subscription ${subscription.id} status: ${subscription.status}`
319
+ );
320
+ }
321
+ async handleSubscriptionDeleted(subscription) {
322
+ console.log(`Subscription deleted: ${subscription.id}`);
323
+ if (this.handlers.onCustomerSubscriptionDeleted) {
324
+ await this.handlers.onCustomerSubscriptionDeleted(subscription);
325
+ }
326
+ console.log(
327
+ `Customer ${subscription.customer} cancelled subscription ${subscription.id}`
328
+ );
329
+ }
330
+ async handleInvoicePaymentSucceeded(invoice) {
331
+ console.log(`Invoice payment succeeded: ${invoice.id}`);
332
+ if (this.handlers.onInvoicePaymentSucceeded) {
333
+ await this.handlers.onInvoicePaymentSucceeded(invoice);
334
+ }
335
+ if (invoice.subscription) {
336
+ console.log(`Payment succeeded for subscription ${invoice.subscription}`);
337
+ }
338
+ }
339
+ async handleInvoicePaymentFailed(invoice) {
340
+ console.log(`Invoice payment failed: ${invoice.id}`);
341
+ if (this.handlers.onInvoicePaymentFailed) {
342
+ await this.handlers.onInvoicePaymentFailed(invoice);
343
+ }
344
+ if (invoice.subscription) {
345
+ console.log(`Payment failed for subscription ${invoice.subscription}`);
346
+ }
347
+ }
348
+ async handleCheckoutSessionCompleted(session) {
349
+ console.log(`Checkout session completed: ${session.id}`);
350
+ if (this.handlers.onCheckoutSessionCompleted) {
351
+ await this.handlers.onCheckoutSessionCompleted(session);
352
+ }
353
+ if (session.mode === "subscription") {
354
+ console.log(
355
+ `Subscription checkout completed for customer ${session.customer}`
356
+ );
357
+ } else {
358
+ console.log(
359
+ `Payment checkout completed for customer ${session.customer}`
360
+ );
361
+ }
362
+ }
363
+ async handlePaymentIntentSucceeded(paymentIntent) {
364
+ console.log(`Payment intent succeeded: ${paymentIntent.id}`);
365
+ if (this.handlers.onPaymentIntentSucceeded) {
366
+ await this.handlers.onPaymentIntentSucceeded(paymentIntent);
367
+ }
368
+ console.log(
369
+ `Payment of ${paymentIntent.amount} ${paymentIntent.currency} succeeded`
370
+ );
371
+ }
372
+ async handlePaymentIntentFailed(paymentIntent) {
373
+ console.log(`Payment intent failed: ${paymentIntent.id}`);
374
+ if (this.handlers.onPaymentIntentPaymentFailed) {
375
+ await this.handlers.onPaymentIntentPaymentFailed(paymentIntent);
376
+ }
377
+ console.log(
378
+ `Payment of ${paymentIntent.amount} ${paymentIntent.currency} failed`
379
+ );
380
+ }
381
+ };
382
+ var createStripeWebhookHandler = (secretKey, handlers = {}) => {
383
+ return new StripeWebhookHandler(secretKey, handlers);
384
+ };
385
+ var createStripeWebhookMiddleware = (secretKey, webhookSecret, handlers = {}) => {
386
+ const webhookHandler = new StripeWebhookHandler(secretKey, handlers);
387
+ return async (req, res, next) => {
388
+ try {
389
+ const signature = req.headers["stripe-signature"];
390
+ if (!signature) {
391
+ return res.status(400).json({ error: "Missing stripe-signature header" });
392
+ }
393
+ const result = await webhookHandler.handleWebhook(
394
+ req.body,
395
+ signature,
396
+ webhookSecret
397
+ );
398
+ if (result.success) {
399
+ res.status(200).json({ received: true });
400
+ } else {
401
+ res.status(400).json({ error: result.error });
402
+ }
403
+ } catch (error) {
404
+ console.error("Webhook middleware error:", error);
405
+ res.status(500).json({ error: "Internal server error" });
406
+ }
407
+ };
408
+ };
409
+ var createNextJSWebhookHandler = (secretKey, webhookSecret, handlers = {}) => {
410
+ const webhookHandler = new StripeWebhookHandler(secretKey, handlers);
411
+ return async (req, res) => {
412
+ if (req.method !== "POST") {
413
+ res.setHeader("Allow", "POST");
414
+ return res.status(405).json({ error: "Method not allowed" });
415
+ }
416
+ try {
417
+ const signature = req.headers["stripe-signature"];
418
+ if (!signature) {
419
+ return res.status(400).json({ error: "Missing stripe-signature header" });
420
+ }
421
+ const result = await webhookHandler.handleWebhook(
422
+ req.body,
423
+ signature,
424
+ webhookSecret
425
+ );
426
+ if (result.success) {
427
+ res.status(200).json({ received: true });
428
+ } else {
429
+ res.status(400).json({ error: result.error });
430
+ }
431
+ } catch (error) {
432
+ console.error("Webhook handler error:", error);
433
+ res.status(500).json({ error: "Internal server error" });
434
+ }
435
+ };
436
+ };
437
+ export {
438
+ StripeServerAPI,
439
+ StripeWebhookHandler,
440
+ createNextJSWebhookHandler,
441
+ createStripeWebhookHandler,
442
+ createStripeWebhookMiddleware
443
+ };