vibes-plug 1.0.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.
Files changed (141) hide show
  1. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  2. package/BLUEPRINT.md +125 -0
  3. package/CHANGELOG.md +195 -0
  4. package/CONTRIBUTING.md +199 -0
  5. package/LICENSE +21 -0
  6. package/README.md +263 -0
  7. package/SECURITY.md +21 -0
  8. package/banner.png +0 -0
  9. package/package.json +25 -0
  10. package/plugin.json +8 -0
  11. package/scripts/update_skills.js +75 -0
  12. package/skills/ai-llm-integration-expert/SKILL.md +162 -0
  13. package/skills/api-design-expert/SKILL.md +310 -0
  14. package/skills/app-analyzer-optimizer/SKILL.md +189 -0
  15. package/skills/asisten_ramah/SKILL.md +41 -0
  16. package/skills/authentication-identity-expert/SKILL.md +45 -0
  17. package/skills/auto-doc-updater/SKILL.md +204 -0
  18. package/skills/bootstrap-to-modern/SKILL.md +87 -0
  19. package/skills/brainstorming/SKILL.md +353 -0
  20. package/skills/bun-runtime-expert/SKILL.md +211 -0
  21. package/skills/ci-cd-devops-architect/SKILL.md +45 -0
  22. package/skills/cloud-hosting-expert/SKILL.md +244 -0
  23. package/skills/coderabbit/SKILL.md +192 -0
  24. package/skills/data-telemetry-expert/SKILL.md +213 -0
  25. package/skills/database-orm-expert/SKILL.md +294 -0
  26. package/skills/design-system-architect/SKILL.md +243 -0
  27. package/skills/e2e-testing-expert/SKILL.md +315 -0
  28. package/skills/event-driven-architect/SKILL.md +81 -0
  29. package/skills/firebase-security-expert/SKILL.md +195 -0
  30. package/skills/fullstack-expert/SKILL.md +202 -0
  31. package/skills/fullstack-expert/references/api_design_guide.md +466 -0
  32. package/skills/fullstack-expert/references/devops_infrastructure.md +477 -0
  33. package/skills/fullstack-expert/references/multi_language_backend.md +528 -0
  34. package/skills/fullstack-expert/references/system_design_patterns.md +358 -0
  35. package/skills/fullstack-expert/scripts/api_contract_validator.py +253 -0
  36. package/skills/fullstack-expert/scripts/architecture_analyzer.py +326 -0
  37. package/skills/gemini-agent-booster/SKILL.md +135 -0
  38. package/skills/global-a11y-i18n-expert/SKILL.md +81 -0
  39. package/skills/go-programming-expert/SKILL.md +295 -0
  40. package/skills/hig/SKILL.md +188 -0
  41. package/skills/js-backend-expert/SKILL.md +192 -0
  42. package/skills/mcp-server-architect/SKILL.md +194 -0
  43. package/skills/mobile-expo-expert/SKILL.md +186 -0
  44. package/skills/monday-design-aesthetic/SKILL.md +67 -0
  45. package/skills/monorepo-architect/SKILL.md +227 -0
  46. package/skills/mpa-orchestrator/SKILL.md +101 -0
  47. package/skills/multi-agent-orchestration/SKILL.md +234 -0
  48. package/skills/multiple-entry-points/SKILL.md +55 -0
  49. package/skills/mvc-expert/SKILL.md +231 -0
  50. package/skills/payment-gateway-expert/SKILL.md +45 -0
  51. package/skills/performance-web-vitals/SKILL.md +332 -0
  52. package/skills/prd-architect/SKILL.md +191 -0
  53. package/skills/production-ready-hardener/SKILL.md +469 -0
  54. package/skills/production-ready-hardener/references/performance_optimization.md +441 -0
  55. package/skills/production-ready-hardener/references/production_checklist.md +161 -0
  56. package/skills/production-ready-hardener/references/security_hardening_guide.md +379 -0
  57. package/skills/production-ready-hardener/scripts/production_readiness_scanner.py +875 -0
  58. package/skills/python-programming-expert/SKILL.md +271 -0
  59. package/skills/realtime-collaboration-expert/SKILL.md +45 -0
  60. package/skills/rust-programming-expert/SKILL.md +235 -0
  61. package/skills/saas-billing/SKILL.md +377 -0
  62. package/skills/saas-multi-tenant/SKILL.md +237 -0
  63. package/skills/saas-mvp-launcher/SKILL.md +231 -0
  64. package/skills/saas-transformer/SKILL.md +446 -0
  65. package/skills/saas-transformer/references/billing_integration_guide.md +401 -0
  66. package/skills/saas-transformer/references/feature_gating_patterns.md +137 -0
  67. package/skills/saas-transformer/references/saas_transformation_checklist.md +121 -0
  68. package/skills/saas-transformer/scripts/saas_transformation_scanner.py +254 -0
  69. package/skills/scalability-clean-code/SKILL.md +229 -0
  70. package/skills/secure-fuzz-testing/SKILL.md +201 -0
  71. package/skills/senior-frontend/SKILL.md +161 -0
  72. package/skills/senior-frontend/references/frontend_best_practices.md +806 -0
  73. package/skills/senior-frontend/references/nextjs_optimization_guide.md +724 -0
  74. package/skills/senior-frontend/references/react_patterns.md +746 -0
  75. package/skills/senior-frontend/scripts/bundle_analyzer.py +407 -0
  76. package/skills/senior-frontend/scripts/component_generator.py +329 -0
  77. package/skills/senior-frontend/scripts/frontend_scaffolder.py +1005 -0
  78. package/skills/senior-fullstack/SKILL.md +167 -0
  79. package/skills/senior-fullstack/references/architecture_patterns.md +160 -0
  80. package/skills/senior-fullstack/references/development_workflows.md +222 -0
  81. package/skills/senior-fullstack/references/tech_stack_guide.md +190 -0
  82. package/skills/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
  83. package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
  84. package/skills/senior-fullstack/scripts/project_scaffolder.py +114 -0
  85. package/skills/seo/SKILL.md +225 -0
  86. package/skills/seo/references/cwv-thresholds.md +108 -0
  87. package/skills/seo/references/eeat-framework.md +214 -0
  88. package/skills/seo/references/quality-gates.md +155 -0
  89. package/skills/seo/references/schema-types.md +118 -0
  90. package/skills/seo-aeo-landing-page-writer/SKILL.md +97 -0
  91. package/skills/seo-geo/SKILL.md +188 -0
  92. package/skills/session-handoff-resume/SKILL.md +158 -0
  93. package/skills/skill_baru/SKILL.md +147 -0
  94. package/skills/spa-orchestrator/SKILL.md +288 -0
  95. package/skills/state-management-expert/SKILL.md +272 -0
  96. package/skills/supabase-migration/SKILL.md +45 -0
  97. package/skills/supabase-security-expert/SKILL.md +243 -0
  98. package/skills/tailwind-expert/SKILL.md +188 -0
  99. package/skills/tanstack-query-expert/SKILL.md +199 -0
  100. package/skills/tauri-expert/SKILL.md +97 -0
  101. package/skills/token-saver/SKILL.md +111 -0
  102. package/skills/typescript-expert/SKILL.md +279 -0
  103. package/skills/ui-components-expert/SKILL.md +63 -0
  104. package/skills/ui-ux-pro-max/SKILL.md +201 -0
  105. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  106. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  107. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  108. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  109. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  110. package/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  111. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  112. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  113. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  114. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  115. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  116. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  117. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  118. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  119. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  120. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  121. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  122. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  123. package/skills/ui-ux-pro-max/data/styles.csv +59 -0
  124. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  125. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  126. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  127. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  128. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
  129. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  130. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
  131. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
  132. package/skills/ui-ux-pro-max/scripts/core.py +257 -0
  133. package/skills/ui-ux-pro-max/scripts/design_system.py +493 -0
  134. package/skills/ui-ux-pro-max/scripts/search.py +81 -0
  135. package/skills/ui_ux_expert/SKILL.md +114 -0
  136. package/skills/vibe-code-gardener/SKILL.md +173 -0
  137. package/skills/web-scraper/SKILL.md +205 -0
  138. package/skills/web-scraper/references/data-transforms.md +397 -0
  139. package/skills/web-scraper/references/extraction-patterns.md +475 -0
  140. package/skills/web-scraper/references/output-templates.md +481 -0
  141. package/skills/zero-to-prod-orchestrator/SKILL.md +180 -0
@@ -0,0 +1,401 @@
1
+ # Billing & Payment Gateway Integration Guide
2
+
3
+ A comprehensive guide to setting up subscription billing, payment gateways, and Merchant of Record (MoR) systems. This guide covers Stripe, Midtrans, PayPal, Paddle, and FastSpring, detailing database structures and webhook verification in a multi-tenant SaaS application.
4
+
5
+ ---
6
+
7
+ ## 1. Gateway Type: Payment Gateway vs. Merchant of Record (MoR)
8
+
9
+ Before choosing a billing system, it is vital to understand the two main categories of billing platforms:
10
+
11
+ | Feature | Payment Gateway (Stripe, Midtrans, PayPal) | Merchant of Record (Paddle, FastSpring) |
12
+ |---|---|---|
13
+ | **Definition** | Processes payments but places legal/financial responsibilities on you. | Acts as the reseller of your software; handles transactions and compliance. |
14
+ | **Sales Tax / VAT** | You must calculate, collect, and file taxes globally (using tools like Stripe Tax). | Automatically handles global taxes, compliance, and invoicing. |
15
+ | **Chargeback Liability**| You manage chargeback disputes directly. | The MoR platform handles chargeback disputes. |
16
+ | **Integration Complexity**| Highly customizable APIs, but requires more backend logic for compliance. | Simple to configure; checkout UI is managed by the MoR. |
17
+
18
+ ---
19
+
20
+ ## 2. Generic Multi-Gateway Database Schema
21
+
22
+ To support multiple payment gateways, use a generic subscription schema.
23
+
24
+ ### Prisma Schema (`prisma/schema.prisma`)
25
+ ```prisma
26
+ model Subscription {
27
+ id String @id @default(cuid())
28
+ workspaceId String @unique
29
+ workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
30
+ gateway String // "stripe", "midtrans", "paypal", "paddle", "fastspring"
31
+ gatewayCustomerId String? @unique
32
+ gatewaySubscriptionId String? @unique
33
+ gatewayPriceId String?
34
+ status String // "active", "trialing", "past_due", "canceled", "unpaid"
35
+ currentPeriodEnd DateTime
36
+ cancelAtPeriodEnd Boolean @default(false)
37
+ createdAt DateTime @default(now())
38
+ updatedAt DateTime @updatedAt
39
+ }
40
+ ```
41
+
42
+ ### Drizzle ORM Schema (`lib/db/schema.ts`)
43
+ ```typescript
44
+ import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';
45
+
46
+ export const subscriptions = pgTable('subscriptions', {
47
+ id: text('id').primaryKey(),
48
+ workspaceId: text('workspace_id').notNull().unique(),
49
+ gateway: text('gateway').notNull(), // "stripe", "midtrans", "paypal", "paddle", "fastspring"
50
+ gatewayCustomerId: text('gateway_customer_id').unique(),
51
+ gatewaySubscriptionId: text('gateway_subscription_id').unique(),
52
+ gatewayPriceId: text('gateway_price_id'),
53
+ status: text('status').notNull(),
54
+ currentPeriodEnd: timestamp('current_period_end').notNull(),
55
+ cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false).notNull(),
56
+ createdAt: timestamp('created_at').defaultNow().notNull(),
57
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
58
+ });
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 3. Webhook Implementations
64
+
65
+ Webhooks are crucial to keep your SaaS database in sync with payment updates. Below are the implementation templates for Next.js 15 (App Router).
66
+
67
+ ### A. Stripe Integration
68
+ Processes subscriptions and invoices. Verified via the Stripe SDK.
69
+
70
+ ```typescript
71
+ import { NextResponse } from 'next/server';
72
+ import Stripe from 'stripe';
73
+ import { db } from '@/lib/db';
74
+ import { subscriptions } from '@/lib/db/schema';
75
+ import { eq } from 'drizzle-orm';
76
+
77
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2025-01-01' as any });
78
+
79
+ export async function POST(req: Request) {
80
+ const body = await req.text();
81
+ const signature = req.headers.get('Stripe-Signature')!;
82
+
83
+ let event: Stripe.Event;
84
+ try {
85
+ event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
86
+ } catch (err: any) {
87
+ return NextResponse.json({ error: `Signature verification failed: ${err.message}` }, { status: 400 });
88
+ }
89
+
90
+ switch (event.type) {
91
+ case 'checkout.session.completed': {
92
+ const session = event.data.object as Stripe.Checkout.Session;
93
+ const workspaceId = session.metadata?.workspaceId;
94
+ if (!workspaceId) break;
95
+
96
+ const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
97
+ await db.insert(subscriptions).values({
98
+ id: subscription.id,
99
+ workspaceId,
100
+ gateway: 'stripe',
101
+ gatewayCustomerId: session.customer as string,
102
+ gatewaySubscriptionId: subscription.id,
103
+ gatewayPriceId: subscription.items.data[0].price.id,
104
+ status: subscription.status,
105
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
106
+ });
107
+ break;
108
+ }
109
+ case 'invoice.payment_succeeded': {
110
+ const invoice = event.data.object as Stripe.Invoice;
111
+ if (!invoice.subscription) break;
112
+ const subscription = await stripe.subscriptions.retrieve(invoice.subscription as string);
113
+ await db.update(subscriptions)
114
+ .set({
115
+ status: subscription.status,
116
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
117
+ })
118
+ .where(eq(subscriptions.gatewaySubscriptionId, subscription.id));
119
+ break;
120
+ }
121
+ case 'customer.subscription.deleted': {
122
+ const subscription = event.data.object as Stripe.Subscription;
123
+ await db.update(subscriptions)
124
+ .set({ status: 'canceled' })
125
+ .where(eq(subscriptions.gatewaySubscriptionId, subscription.id));
126
+ break;
127
+ }
128
+ }
129
+ return NextResponse.json({ received: true });
130
+ }
131
+ ```
132
+
133
+ ### B. Midtrans Integration
134
+ Southeast Asia's popular payment gateway. Verified via SHA512 signature key matching.
135
+
136
+ ```typescript
137
+ import { NextResponse } from 'next/server';
138
+ import crypto from 'crypto';
139
+ import { db } from '@/lib/db';
140
+ import { subscriptions } from '@/lib/db/schema';
141
+ import { eq } from 'drizzle-orm';
142
+
143
+ export async function POST(req: Request) {
144
+ const body = await req.json();
145
+ const { order_id, status_code, gross_amount, signature_key, transaction_status, fraud_status } = body;
146
+
147
+ // Verify signature key: sha512(order_id + status_code + gross_amount + server_key)
148
+ const serverKey = process.env.MIDTRANS_SERVER_KEY!;
149
+ const hashed = crypto
150
+ .createHash('sha512')
151
+ .update(order_id + status_code + gross_amount + serverKey)
152
+ .digest('hex');
153
+
154
+ if (hashed !== signature_key) {
155
+ return NextResponse.json({ error: 'Invalid Midtrans signature' }, { status: 400 });
156
+ }
157
+
158
+ // Update subscription/payment state based on transaction_status
159
+ if (transaction_status === 'capture' || transaction_status === 'settlement') {
160
+ if (fraud_status === 'challenge') {
161
+ await db.update(subscriptions).set({ status: 'unpaid' }).where(eq(subscriptions.id, order_id));
162
+ } else {
163
+ await db.update(subscriptions)
164
+ .set({ status: 'active', currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) })
165
+ .where(eq(subscriptions.id, order_id));
166
+ }
167
+ } else if (['cancel', 'deny', 'expire'].includes(transaction_status)) {
168
+ await db.update(subscriptions).set({ status: 'canceled' }).where(eq(subscriptions.id, order_id));
169
+ } else if (transaction_status === 'pending') {
170
+ await db.update(subscriptions).set({ status: 'unpaid' }).where(eq(subscriptions.id, order_id));
171
+ }
172
+
173
+ return NextResponse.json({ success: true });
174
+ }
175
+ ```
176
+
177
+ ### C. PayPal Integration
178
+ Global subscription billing. Verified via PayPal API request validation.
179
+
180
+ ```typescript
181
+ import { NextResponse } from 'next/server';
182
+ import { db } from '@/lib/db';
183
+ import { subscriptions } from '@/lib/db/schema';
184
+ import { eq } from 'drizzle-orm';
185
+
186
+ async function verifyPayPalSignature(headers: Headers, rawBody: string, webhookId: string) {
187
+ // Call PayPal API to verify signature authenticity
188
+ const endpoint = process.env.PAYPAL_MODE === 'live'
189
+ ? 'https://api-m.paypal.com'
190
+ : 'https://api-m.sandbox.paypal.com';
191
+
192
+ const authHeader = headers.get('authorization');
193
+ if (!authHeader) return false;
194
+
195
+ const response = await fetch(`${endpoint}/v1/notifications/verify-webhook-signature`, {
196
+ method: 'POST',
197
+ headers: {
198
+ 'Content-Type': 'application/json',
199
+ 'Authorization': authHeader,
200
+ },
201
+ body: JSON.stringify({
202
+ auth_algo: headers.get('paypal-auth-algo'),
203
+ cert_url: headers.get('paypal-cert-url'),
204
+ transmission_id: headers.get('paypal-transmission-id'),
205
+ transmission_sig: headers.get('paypal-transmission-sig'),
206
+ transmission_time: headers.get('paypal-transmission-time'),
207
+ webhook_id: webhookId,
208
+ webhook_event: JSON.parse(rawBody),
209
+ }),
210
+ });
211
+
212
+ const verification = await response.json();
213
+ return verification.verification_status === 'SUCCESS';
214
+ }
215
+
216
+ export async function POST(req: Request) {
217
+ const rawBody = await req.text();
218
+ const webhookId = process.env.PAYPAL_WEBHOOK_ID!;
219
+
220
+ const isValid = await verifyPayPalSignature(req.headers, rawBody, webhookId);
221
+ if (!isValid) {
222
+ return NextResponse.json({ error: 'PayPal signature invalid' }, { status: 400 });
223
+ }
224
+
225
+ const event = JSON.parse(rawBody);
226
+
227
+ switch (event.event_type) {
228
+ case 'BILLING.SUBSCRIPTION.CREATED': {
229
+ const sub = event.resource;
230
+ const workspaceId = sub.custom_id; // Pass workspace ID in custom_id
231
+ if (!workspaceId) break;
232
+
233
+ await db.insert(subscriptions).values({
234
+ id: sub.id,
235
+ workspaceId,
236
+ gateway: 'paypal',
237
+ gatewayCustomerId: sub.subscriber.payer_id,
238
+ gatewaySubscriptionId: sub.id,
239
+ gatewayPriceId: sub.plan_id,
240
+ status: 'active',
241
+ currentPeriodEnd: new Date(sub.billing_info.next_billing_time),
242
+ });
243
+ break;
244
+ }
245
+ case 'BILLING.SUBSCRIPTION.CANCELLED': {
246
+ const sub = event.resource;
247
+ await db.update(subscriptions)
248
+ .set({ status: 'canceled' })
249
+ .where(eq(subscriptions.gatewaySubscriptionId, sub.id));
250
+ break;
251
+ }
252
+ }
253
+
254
+ return NextResponse.json({ received: true });
255
+ }
256
+ ```
257
+
258
+ ### D. Paddle Integration (Paddle Billing v2)
259
+ Merchant of Record. Verified locally using an HMAC-SHA256 signature key verification.
260
+
261
+ ```typescript
262
+ import { NextResponse } from 'next/server';
263
+ import crypto from 'crypto';
264
+ import { db } from '@/lib/db';
265
+ import { subscriptions } from '@/lib/db/schema';
266
+ import { eq } from 'drizzle-orm';
267
+
268
+ function verifyPaddleSignature(body: string, signatureHeader: string, secret: string): boolean {
269
+ const parts = signatureHeader.split(';');
270
+ const tsPart = parts.find(p => p.startsWith('ts='));
271
+ const h1Part = parts.find(p => p.startsWith('h1='));
272
+ if (!tsPart || !h1Part) return false;
273
+
274
+ const ts = tsPart.split('=')[1];
275
+ const h1 = h1Part.split('=')[1];
276
+
277
+ const payload = `${ts}:${body}`;
278
+ const computedHash = crypto.createHmac('sha256', secret).update(payload).digest('hex');
279
+ return computedHash === h1;
280
+ }
281
+
282
+ export async function POST(req: Request) {
283
+ const rawBody = await req.text();
284
+ const signature = req.headers.get('Paddle-Signature')!;
285
+ const secret = process.env.PADDLE_WEBHOOK_SECRET!;
286
+
287
+ if (!verifyPaddleSignature(rawBody, signature, secret)) {
288
+ return NextResponse.json({ error: 'Invalid Paddle signature' }, { status: 400 });
289
+ }
290
+
291
+ const event = JSON.parse(rawBody);
292
+ const eventType = event.event_type;
293
+ const data = event.data;
294
+
295
+ switch (eventType) {
296
+ case 'subscription.created':
297
+ case 'subscription.updated': {
298
+ const workspaceId = data.custom_data?.workspaceId;
299
+ if (!workspaceId) break;
300
+
301
+ await db.insert(subscriptions).values({
302
+ id: data.id,
303
+ workspaceId,
304
+ gateway: 'paddle',
305
+ gatewayCustomerId: data.customer_id,
306
+ gatewaySubscriptionId: data.id,
307
+ gatewayPriceId: data.items[0].price.id,
308
+ status: data.status, // active, trialing, paused
309
+ currentPeriodEnd: new Date(data.current_billing_period.ends_at),
310
+ }).onConflictDoUpdate({
311
+ target: subscriptions.workspaceId,
312
+ set: {
313
+ status: data.status,
314
+ currentPeriodEnd: new Date(data.current_billing_period.ends_at),
315
+ gatewayPriceId: data.items[0].price.id,
316
+ }
317
+ });
318
+ break;
319
+ }
320
+ case 'subscription.canceled': {
321
+ await db.update(subscriptions)
322
+ .set({ status: 'canceled' })
323
+ .where(eq(subscriptions.gatewaySubscriptionId, data.id));
324
+ break;
325
+ }
326
+ }
327
+
328
+ return NextResponse.json({ received: true });
329
+ }
330
+ ```
331
+
332
+ ### E. FastSpring Integration
333
+ Merchant of Record. Verified via an HMAC-SHA256 signature key verification.
334
+
335
+ ```typescript
336
+ import { NextResponse } from 'next/server';
337
+ import crypto from 'crypto';
338
+ import { db } from '@/lib/db';
339
+ import { subscriptions } from '@/lib/db/schema';
340
+ import { eq } from 'drizzle-orm';
341
+
342
+ function verifyFastSpringSignature(body: string, signature: string, secretKey: string): boolean {
343
+ const hash = crypto.createHmac('sha256', secretKey).update(body).digest('base64');
344
+ return hash === signature;
345
+ }
346
+
347
+ export async function POST(req: Request) {
348
+ const rawBody = await req.text();
349
+ const signature = req.headers.get('X-FS-Signature')!;
350
+ const secretKey = process.env.FASTSPRING_SHARED_SECRET!;
351
+
352
+ if (!verifyFastSpringSignature(rawBody, signature, secretKey)) {
353
+ return NextResponse.json({ error: 'Invalid FastSpring signature' }, { status: 400 });
354
+ }
355
+
356
+ const payload = JSON.parse(rawBody);
357
+
358
+ for (const event of payload.events) {
359
+ switch (event.type) {
360
+ case 'subscription.activated': {
361
+ const sub = event.data;
362
+ const workspaceId = sub.tags?.workspaceId;
363
+ if (!workspaceId) break;
364
+
365
+ await db.insert(subscriptions).values({
366
+ id: sub.id,
367
+ workspaceId,
368
+ gateway: 'fastspring',
369
+ gatewayCustomerId: sub.account,
370
+ gatewaySubscriptionId: sub.id,
371
+ gatewayPriceId: sub.product,
372
+ status: 'active',
373
+ currentPeriodEnd: new Date(sub.nextSession),
374
+ });
375
+ break;
376
+ }
377
+ case 'subscription.canceled': {
378
+ const sub = event.data;
379
+ await db.update(subscriptions)
380
+ .set({ status: 'canceled' })
381
+ .where(eq(subscriptions.gatewaySubscriptionId, sub.id));
382
+ break;
383
+ }
384
+ }
385
+ }
386
+
387
+ return NextResponse.json({ received: true });
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## 4. Best Practices for Webhook Handlers
394
+
395
+ 1. **Webhook Idempotency**: Payment gateways can redeliver webhooks. Always store a list of processed event IDs in your database to prevent duplicate actions:
396
+ ```typescript
397
+ const processed = await db.query.processedEvents.findFirst({ where: eq(processedEvents.id, eventId) });
398
+ if (processed) return NextResponse.json({ received: true });
399
+ ```
400
+ 2. **Handle Async Work Gracefully**: Send a fast response (`200 OK`) back to the gateway first, then process any heavy logic asynchronously (or via queue systems like BullMQ) if necessary, to avoid gateway timeouts.
401
+ 3. **Database Locks**: Run your update queries inside clean transactions to avoid concurrency anomalies.
@@ -0,0 +1,137 @@
1
+ # Feature Gating & Usage Limits Patterns
2
+
3
+ Standard patterns for restricting access to features and enforcing quotas based on plan configurations.
4
+
5
+ ---
6
+
7
+ ## 1. Feature Map Definitions
8
+
9
+ Define a clear schema mapping plans to features and usage limits.
10
+
11
+ ```typescript
12
+ export interface PlanConfig {
13
+ name: string;
14
+ maxMembers: number;
15
+ maxProjects: number;
16
+ features: {
17
+ advancedReporting: boolean;
18
+ apiAccess: boolean;
19
+ customDomain: boolean;
20
+ };
21
+ }
22
+
23
+ export const PLAN_CONFIGS: Record<string, PlanConfig> = {
24
+ free: {
25
+ name: 'Free',
26
+ maxMembers: 1,
27
+ maxProjects: 3,
28
+ features: {
29
+ advancedReporting: false,
30
+ apiAccess: false,
31
+ customDomain: false
32
+ }
33
+ },
34
+ pro: {
35
+ name: 'Professional',
36
+ maxMembers: 10,
37
+ maxProjects: 50,
38
+ features: {
39
+ advancedReporting: true,
40
+ apiAccess: true,
41
+ customDomain: false
42
+ }
43
+ },
44
+ enterprise: {
45
+ name: 'Enterprise',
46
+ maxMembers: 100,
47
+ maxProjects: 999999,
48
+ features: {
49
+ advancedReporting: true,
50
+ apiAccess: true,
51
+ customDomain: true
52
+ }
53
+ }
54
+ };
55
+ ```
56
+
57
+ ---
58
+
59
+ ## 2. Server-side Gate Keepers (Next.js Actions / Routes)
60
+
61
+ Implement standard assertions in your data access layer.
62
+
63
+ ```typescript
64
+ import { getWorkspaceSubscription } from './billing';
65
+ import { PLAN_CONFIGS } from './plan-configs';
66
+ import { db } from './db';
67
+ import { projects } from './db/schema';
68
+ import { count, eq } from 'drizzle-orm';
69
+
70
+ // Feature gate assertion
71
+ export async function assertHasFeature(workspaceId: string, feature: keyof PlanConfig['features']) {
72
+ const { plan, isActive } = await getWorkspaceSubscription(workspaceId);
73
+ const activePlan = isActive ? plan : 'free';
74
+
75
+ const hasFeature = PLAN_CONFIGS[activePlan]?.features[feature];
76
+ if (!hasFeature) {
77
+ throw new Error(`Your plan (${activePlan}) does not support the feature: ${feature}`);
78
+ }
79
+ }
80
+
81
+ // Usage limits assertion
82
+ export async function assertWithinProjectLimit(workspaceId: string) {
83
+ const { plan, isActive } = await getWorkspaceSubscription(workspaceId);
84
+ const activePlan = isActive ? plan : 'free';
85
+
86
+ const limit = PLAN_CONFIGS[activePlan].maxProjects;
87
+
88
+ const [projectCount] = await db
89
+ .select({ val: count() })
90
+ .from(projects)
91
+ .where(eq(projects.workspaceId, workspaceId));
92
+
93
+ if (projectCount.val >= limit) {
94
+ throw new Error(`Project limit reached (${projectCount.val}/${limit}). Please upgrade your plan.`);
95
+ }
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 3. UI Graceful Degradation / Upgrade Banner
102
+
103
+ Wrap component gates clean in React to encourage high conversion paths.
104
+
105
+ ```tsx
106
+ import React from 'react';
107
+ import { Button } from '@/components/ui/button';
108
+
109
+ interface UpgradeGuardProps {
110
+ hasFeature: boolean;
111
+ featureName: string;
112
+ children: React.ReactNode;
113
+ }
114
+
115
+ export function UpgradeGuard({ hasFeature, featureName, children }: UpgradeGuardProps) {
116
+ if (hasFeature) {
117
+ return <>{children}</>;
118
+ }
119
+
120
+ return (
121
+ <div className="relative border border-dashed border-gray-300 rounded-lg p-8 text-center bg-gray-50/50">
122
+ <div className="absolute inset-0 bg-white/40 backdrop-blur-[1px] rounded-lg" />
123
+ <div className="relative z-10 max-w-sm mx-auto">
124
+ <h3 className="text-lg font-semibold text-gray-900">Unlock {featureName}</h3>
125
+ <p className="mt-2 text-sm text-gray-500">
126
+ This feature is available on our Professional and Enterprise plans. Upgrade today to scale.
127
+ </p>
128
+ <div className="mt-4">
129
+ <Button href="/settings/billing" variant="default">
130
+ View Pricing Plans
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ );
136
+ }
137
+ ```
@@ -0,0 +1,121 @@
1
+ # SaaS Transformation Checklist
2
+
3
+ Detailed verification checkpoints and implementation patterns for converting a single-tenant application to a multi-tenant SaaS platform.
4
+
5
+ ---
6
+
7
+ ## Phase 1: Discovery & Analysis
8
+
9
+ - [ ] **Data Model Mapping**: Extract all relationships. Identify which tables are "global" (shared reference data, e.g., plan configurations, country list) and which are "tenant-specific" (user data, posts, transactions).
10
+ - [ ] **Auth Audit**: Check if the app uses native session storage, custom cookies, or third-party auth. Identify where users are created and stored.
11
+ - [ ] **API Access Points**: Audit all public and internal route files. Note down endpoints that lack authentication.
12
+ - [ ] **Pricing Strategy Fit**: Document the exact plan rules.
13
+ - *Example*: Free Plan has 1 project limit. Pro Plan has unlimited projects.
14
+
15
+ ---
16
+
17
+ ## Phase 2: Multi-Tenancy Foundation
18
+
19
+ ### PostgreSQL Row-Level Security (RLS) Pattern
20
+ When using a shared-schema model, configure your tables to automatically isolate data.
21
+
22
+ ```sql
23
+ -- 1. Enable RLS
24
+ ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
25
+ ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
26
+
27
+ -- 2. Create helper function to retrieve current tenant from session context
28
+ CREATE OR REPLACE FUNCTION auth.current_tenant_id()
29
+ RETURNS UUID AS $$
30
+ SELECT NULLIF(current_setting('app.current_tenant_id', true), '')::UUID;
31
+ $$ LANGUAGE sql STABLE;
32
+
33
+ -- 3. Apply policies using membership lookup
34
+ CREATE POLICY tenant_isolation_policy ON projects
35
+ AS RESTRICTIVE
36
+ USING (
37
+ workspace_id = auth.current_tenant_id()
38
+ );
39
+ ```
40
+
41
+ ### Middleware Connection Context Setting (Node.js/Express Example)
42
+ Ensure every database transaction or connection sets the `app.current_tenant_id` session configuration.
43
+
44
+ ```typescript
45
+ import { Request, Response, NextFunction } from 'express';
46
+ import { db } from '../lib/db';
47
+
48
+ export async function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
49
+ const tenantId = req.headers['x-tenant-id'] || req.user?.workspaceId;
50
+
51
+ if (!tenantId) {
52
+ return res.status(400).json({ error: 'Tenant context missing' });
53
+ }
54
+
55
+ // Inject session variable inside transaction wrapper or pool client
56
+ await db.execute(sql`SET LOCAL app.current_tenant_id = ${tenantId}`);
57
+ next();
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Phase 3: Authentication & Authorization
64
+
65
+ - [ ] **Workspace Member Resolution**: Ensure a user is mapped to multiple workspaces via a join table (`workspace_members`).
66
+ - [ ] **Role-Based Checkpoint**:
67
+ ```typescript
68
+ export function requireRole(allowedRoles: ('owner' | 'admin' | 'member')[]) {
69
+ return (req: Request, res: Response, next: NextFunction) => {
70
+ const userRole = req.user?.role;
71
+ if (!userRole || !allowedRoles.includes(userRole)) {
72
+ return res.status(403).json({ error: 'Permission denied' });
73
+ }
74
+ next();
75
+ };
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Phase 4: Billing & Subscription
82
+
83
+ - [ ] **Stripe Event Handling Idempotency**: Save all processed Stripe Event IDs to a database log table to prevent processing webhooks twice.
84
+ - [ ] **Dunning Flow**: Set up Stripe triggers for `invoice.payment_failed` to send a dynamic link allowing the user to update their payment card in one click.
85
+
86
+ ---
87
+
88
+ ## Phase 5: Workspace & Team Management
89
+
90
+ - [ ] **Invitation Tokens**: Generate cryptographically secure invite tokens using standard library utilities:
91
+ ```typescript
92
+ import crypto from 'crypto';
93
+ const token = crypto.randomBytes(32).toString('hex');
94
+ ```
95
+ - [ ] **Workspace Switcher Logic**: When changing workspaces, update the session token (or JWT) and redirect the user back to the application dashboard to force frontend hydration with new data.
96
+
97
+ ---
98
+
99
+ ## Phase 6: SaaS Frontend & Landing
100
+
101
+ - [ ] **Responsive Navigation**: Use dynamic dashboard layouts that collapse sidebars on mobile touchpoints.
102
+ - [ ] **SEO Configuration**: Output unique structured data per landing section.
103
+
104
+ ---
105
+
106
+ ## Phase 7: API Layer & Feature Gating
107
+
108
+ - [ ] **Feature Flags**: Guard pages and controls using a hook pattern:
109
+ ```typescript
110
+ const { hasFeature } = useSubscription();
111
+ if (!hasFeature('advanced-export')) {
112
+ return <UpgradeBanner featureName="Advanced Export" />;
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Phase 8: Production Hardening & Launch
119
+
120
+ - [ ] **Rollback Runbook**: Create rollback scripts for DB migrations.
121
+ - [ ] **Cascade Deletion Tests**: Ensure deleting a workspace cleans up all child tables cleanly without foreign-key orphan exceptions.