@girardmedia/bootspring 1.2.0 → 2.0.3

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 (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -1,279 +0,0 @@
1
- # Push Notification Patterns
2
-
3
- Patterns for web push notifications.
4
-
5
- ## Service Worker Setup
6
-
7
- ```typescript
8
- // public/sw.js
9
- self.addEventListener('push', (event) => {
10
- const data = event.data?.json() ?? {}
11
-
12
- const options = {
13
- body: data.body,
14
- icon: data.icon || '/icon-192.png',
15
- badge: '/badge.png',
16
- data: { url: data.url },
17
- actions: data.actions || []
18
- }
19
-
20
- event.waitUntil(
21
- self.registration.showNotification(data.title, options)
22
- )
23
- })
24
-
25
- self.addEventListener('notificationclick', (event) => {
26
- event.notification.close()
27
-
28
- const url = event.notification.data?.url || '/'
29
-
30
- event.waitUntil(
31
- clients.matchAll({ type: 'window' }).then((clientList) => {
32
- for (const client of clientList) {
33
- if (client.url === url && 'focus' in client) {
34
- return client.focus()
35
- }
36
- }
37
- return clients.openWindow(url)
38
- })
39
- )
40
- })
41
- ```
42
-
43
- ## Push Subscription
44
-
45
- ```tsx
46
- // hooks/usePushNotifications.ts
47
- 'use client'
48
-
49
- import { useState, useEffect } from 'react'
50
-
51
- const VAPID_PUBLIC_KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!
52
-
53
- export function usePushNotifications() {
54
- const [permission, setPermission] = useState<NotificationPermission>('default')
55
- const [subscription, setSubscription] = useState<PushSubscription | null>(null)
56
-
57
- useEffect(() => {
58
- if ('Notification' in window) {
59
- setPermission(Notification.permission)
60
- }
61
- }, [])
62
-
63
- async function subscribe() {
64
- try {
65
- // Request permission
66
- const perm = await Notification.requestPermission()
67
- setPermission(perm)
68
-
69
- if (perm !== 'granted') return null
70
-
71
- // Register service worker
72
- const registration = await navigator.serviceWorker.register('/sw.js')
73
-
74
- // Subscribe to push
75
- const sub = await registration.pushManager.subscribe({
76
- userVisibleOnly: true,
77
- applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
78
- })
79
-
80
- setSubscription(sub)
81
-
82
- // Send subscription to server
83
- await fetch('/api/push/subscribe', {
84
- method: 'POST',
85
- headers: { 'Content-Type': 'application/json' },
86
- body: JSON.stringify(sub.toJSON())
87
- })
88
-
89
- return sub
90
- } catch (error) {
91
- console.error('Push subscription failed:', error)
92
- return null
93
- }
94
- }
95
-
96
- async function unsubscribe() {
97
- if (!subscription) return
98
-
99
- await subscription.unsubscribe()
100
-
101
- await fetch('/api/push/unsubscribe', {
102
- method: 'POST',
103
- headers: { 'Content-Type': 'application/json' },
104
- body: JSON.stringify({ endpoint: subscription.endpoint })
105
- })
106
-
107
- setSubscription(null)
108
- }
109
-
110
- return {
111
- permission,
112
- subscription,
113
- subscribe,
114
- unsubscribe,
115
- isSupported: 'PushManager' in window
116
- }
117
- }
118
-
119
- function urlBase64ToUint8Array(base64String: string) {
120
- const padding = '='.repeat((4 - base64String.length % 4) % 4)
121
- const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
122
- const rawData = window.atob(base64)
123
- return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)))
124
- }
125
- ```
126
-
127
- ## Server-Side Push
128
-
129
- ```typescript
130
- // lib/push.ts
131
- import webpush from 'web-push'
132
- import { prisma } from '@/lib/db'
133
-
134
- webpush.setVapidDetails(
135
- 'mailto:support@myapp.com',
136
- process.env.VAPID_PUBLIC_KEY!,
137
- process.env.VAPID_PRIVATE_KEY!
138
- )
139
-
140
- interface PushPayload {
141
- title: string
142
- body: string
143
- icon?: string
144
- url?: string
145
- }
146
-
147
- export async function sendPushNotification(userId: string, payload: PushPayload) {
148
- const subscriptions = await prisma.pushSubscription.findMany({
149
- where: { userId }
150
- })
151
-
152
- const results = await Promise.allSettled(
153
- subscriptions.map(async (sub) => {
154
- try {
155
- await webpush.sendNotification(
156
- {
157
- endpoint: sub.endpoint,
158
- keys: {
159
- p256dh: sub.p256dh,
160
- auth: sub.auth
161
- }
162
- },
163
- JSON.stringify(payload)
164
- )
165
- } catch (error: any) {
166
- // Remove invalid subscriptions
167
- if (error.statusCode === 410 || error.statusCode === 404) {
168
- await prisma.pushSubscription.delete({
169
- where: { id: sub.id }
170
- })
171
- }
172
- throw error
173
- }
174
- })
175
- )
176
-
177
- return results
178
- }
179
-
180
- // Usage
181
- await sendPushNotification(userId, {
182
- title: 'New Message',
183
- body: 'You have a new message from John',
184
- url: '/messages/123'
185
- })
186
- ```
187
-
188
- ## Subscribe API
189
-
190
- ```typescript
191
- // app/api/push/subscribe/route.ts
192
- import { auth } from '@/auth'
193
- import { prisma } from '@/lib/db'
194
-
195
- export async function POST(request: Request) {
196
- const session = await auth()
197
- if (!session) {
198
- return Response.json({ error: 'Unauthorized' }, { status: 401 })
199
- }
200
-
201
- const subscription = await request.json()
202
-
203
- await prisma.pushSubscription.upsert({
204
- where: { endpoint: subscription.endpoint },
205
- create: {
206
- userId: session.user.id,
207
- endpoint: subscription.endpoint,
208
- p256dh: subscription.keys.p256dh,
209
- auth: subscription.keys.auth
210
- },
211
- update: {
212
- p256dh: subscription.keys.p256dh,
213
- auth: subscription.keys.auth
214
- }
215
- })
216
-
217
- return Response.json({ success: true })
218
- }
219
- ```
220
-
221
- ## Notification Preferences
222
-
223
- ```tsx
224
- // components/NotificationSettings.tsx
225
- 'use client'
226
-
227
- import { usePushNotifications } from '@/hooks/usePushNotifications'
228
-
229
- export function NotificationSettings() {
230
- const { permission, subscription, subscribe, unsubscribe, isSupported } = usePushNotifications()
231
-
232
- if (!isSupported) {
233
- return <p>Push notifications are not supported in this browser.</p>
234
- }
235
-
236
- return (
237
- <div className="space-y-4">
238
- <div className="flex items-center justify-between">
239
- <div>
240
- <p className="font-medium">Push Notifications</p>
241
- <p className="text-sm text-gray-500">
242
- {subscription ? 'Enabled' : 'Disabled'}
243
- </p>
244
- </div>
245
-
246
- {subscription ? (
247
- <button
248
- onClick={unsubscribe}
249
- className="rounded bg-red-100 px-4 py-2 text-red-700"
250
- >
251
- Disable
252
- </button>
253
- ) : (
254
- <button
255
- onClick={subscribe}
256
- disabled={permission === 'denied'}
257
- className="rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
258
- >
259
- Enable
260
- </button>
261
- )}
262
- </div>
263
-
264
- {permission === 'denied' && (
265
- <p className="text-sm text-red-600">
266
- Notifications are blocked. Please enable them in your browser settings.
267
- </p>
268
- )}
269
- </div>
270
- )
271
- }
272
- ```
273
-
274
- ## When to Use
275
-
276
- - Real-time updates
277
- - Message notifications
278
- - Reminders
279
- - Marketing engagement
@@ -1,303 +0,0 @@
1
- # Checkout Patterns
2
-
3
- Patterns for payment checkout flows with Stripe.
4
-
5
- ## Checkout Session
6
-
7
- ```typescript
8
- // lib/checkout.ts
9
- import Stripe from 'stripe'
10
-
11
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
12
-
13
- export async function createCheckoutSession({
14
- userId,
15
- priceId,
16
- mode = 'subscription',
17
- successUrl,
18
- cancelUrl
19
- }: {
20
- userId: string
21
- priceId: string
22
- mode?: 'subscription' | 'payment'
23
- successUrl?: string
24
- cancelUrl?: string
25
- }) {
26
- const user = await prisma.user.findUnique({
27
- where: { id: userId }
28
- })
29
-
30
- if (!user) throw new Error('User not found')
31
-
32
- // Get or create customer
33
- let customerId = user.stripeCustomerId
34
-
35
- if (!customerId) {
36
- const customer = await stripe.customers.create({
37
- email: user.email,
38
- name: user.name ?? undefined,
39
- metadata: { userId }
40
- })
41
- customerId = customer.id
42
-
43
- await prisma.user.update({
44
- where: { id: userId },
45
- data: { stripeCustomerId: customerId }
46
- })
47
- }
48
-
49
- const session = await stripe.checkout.sessions.create({
50
- customer: customerId,
51
- mode,
52
- payment_method_types: ['card'],
53
- line_items: [{ price: priceId, quantity: 1 }],
54
- success_url: successUrl ?? `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
55
- cancel_url: cancelUrl ?? `${process.env.NEXT_PUBLIC_APP_URL}/checkout/canceled`,
56
- metadata: { userId },
57
- allow_promotion_codes: true,
58
- billing_address_collection: 'auto',
59
- tax_id_collection: { enabled: true }
60
- })
61
-
62
- return session
63
- }
64
- ```
65
-
66
- ## Checkout API Route
67
-
68
- ```typescript
69
- // app/api/checkout/route.ts
70
- import { auth } from '@/auth'
71
- import { createCheckoutSession } from '@/lib/checkout'
72
-
73
- export async function POST(request: Request) {
74
- const session = await auth()
75
-
76
- if (!session?.user) {
77
- return Response.json({ error: 'Unauthorized' }, { status: 401 })
78
- }
79
-
80
- const { priceId, mode } = await request.json()
81
-
82
- if (!priceId) {
83
- return Response.json({ error: 'Price ID required' }, { status: 400 })
84
- }
85
-
86
- try {
87
- const checkoutSession = await createCheckoutSession({
88
- userId: session.user.id,
89
- priceId,
90
- mode
91
- })
92
-
93
- return Response.json({ url: checkoutSession.url })
94
- } catch (error) {
95
- console.error('Checkout error:', error)
96
- return Response.json({ error: 'Failed to create checkout' }, { status: 500 })
97
- }
98
- }
99
- ```
100
-
101
- ## Checkout Button
102
-
103
- ```tsx
104
- // components/checkout/CheckoutButton.tsx
105
- 'use client'
106
-
107
- import { useState } from 'react'
108
- import { Button } from '@/components/ui/Button'
109
-
110
- interface Props {
111
- priceId: string
112
- mode?: 'subscription' | 'payment'
113
- children: React.ReactNode
114
- }
115
-
116
- export function CheckoutButton({ priceId, mode = 'subscription', children }: Props) {
117
- const [loading, setLoading] = useState(false)
118
-
119
- async function handleCheckout() {
120
- setLoading(true)
121
-
122
- try {
123
- const response = await fetch('/api/checkout', {
124
- method: 'POST',
125
- headers: { 'Content-Type': 'application/json' },
126
- body: JSON.stringify({ priceId, mode })
127
- })
128
-
129
- const { url, error } = await response.json()
130
-
131
- if (error) {
132
- throw new Error(error)
133
- }
134
-
135
- window.location.href = url
136
- } catch (error) {
137
- console.error('Checkout failed:', error)
138
- alert('Failed to start checkout')
139
- } finally {
140
- setLoading(false)
141
- }
142
- }
143
-
144
- return (
145
- <Button onClick={handleCheckout} disabled={loading}>
146
- {loading ? 'Loading...' : children}
147
- </Button>
148
- )
149
- }
150
- ```
151
-
152
- ## Success Page
153
-
154
- ```tsx
155
- // app/checkout/success/page.tsx
156
- import { stripe } from '@/lib/stripe'
157
- import { redirect } from 'next/navigation'
158
-
159
- interface Props {
160
- searchParams: { session_id?: string }
161
- }
162
-
163
- export default async function CheckoutSuccessPage({ searchParams }: Props) {
164
- const sessionId = searchParams.session_id
165
-
166
- if (!sessionId) {
167
- redirect('/')
168
- }
169
-
170
- const session = await stripe.checkout.sessions.retrieve(sessionId, {
171
- expand: ['subscription', 'customer']
172
- })
173
-
174
- if (session.payment_status !== 'paid') {
175
- redirect('/checkout/canceled')
176
- }
177
-
178
- return (
179
- <div className="mx-auto max-w-md py-12 text-center">
180
- <div className="mb-4 text-5xl">✓</div>
181
- <h1 className="mb-2 text-2xl font-bold">Payment Successful</h1>
182
- <p className="mb-8 text-gray-600">
183
- Thank you for your purchase. Your subscription is now active.
184
- </p>
185
- <a
186
- href="/dashboard"
187
- className="rounded-lg bg-black px-6 py-3 text-white hover:bg-gray-800"
188
- >
189
- Go to Dashboard
190
- </a>
191
- </div>
192
- )
193
- }
194
- ```
195
-
196
- ## Embedded Checkout
197
-
198
- ```tsx
199
- // components/checkout/EmbeddedCheckout.tsx
200
- 'use client'
201
-
202
- import { useState, useEffect } from 'react'
203
- import { loadStripe } from '@stripe/stripe-js'
204
- import {
205
- EmbeddedCheckout,
206
- EmbeddedCheckoutProvider
207
- } from '@stripe/react-stripe-js'
208
-
209
- const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!)
210
-
211
- interface Props {
212
- priceId: string
213
- }
214
-
215
- export function CheckoutForm({ priceId }: Props) {
216
- const [clientSecret, setClientSecret] = useState('')
217
-
218
- useEffect(() => {
219
- fetch('/api/checkout/embedded', {
220
- method: 'POST',
221
- headers: { 'Content-Type': 'application/json' },
222
- body: JSON.stringify({ priceId })
223
- })
224
- .then(res => res.json())
225
- .then(data => setClientSecret(data.clientSecret))
226
- }, [priceId])
227
-
228
- if (!clientSecret) {
229
- return <div>Loading...</div>
230
- }
231
-
232
- return (
233
- <EmbeddedCheckoutProvider stripe={stripePromise} options={{ clientSecret }}>
234
- <EmbeddedCheckout />
235
- </EmbeddedCheckoutProvider>
236
- )
237
- }
238
-
239
- // API route for embedded checkout
240
- // app/api/checkout/embedded/route.ts
241
- export async function POST(request: Request) {
242
- const session = await auth()
243
- const { priceId } = await request.json()
244
-
245
- const checkoutSession = await stripe.checkout.sessions.create({
246
- ui_mode: 'embedded',
247
- customer: user.stripeCustomerId,
248
- mode: 'subscription',
249
- line_items: [{ price: priceId, quantity: 1 }],
250
- return_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/return?session_id={CHECKOUT_SESSION_ID}`
251
- })
252
-
253
- return Response.json({ clientSecret: checkoutSession.client_secret })
254
- }
255
- ```
256
-
257
- ## One-Time Payment
258
-
259
- ```typescript
260
- // lib/checkout.ts
261
- export async function createPaymentSession({
262
- userId,
263
- amount,
264
- description,
265
- metadata = {}
266
- }: {
267
- userId: string
268
- amount: number
269
- description: string
270
- metadata?: Record<string, string>
271
- }) {
272
- const user = await prisma.user.findUnique({
273
- where: { id: userId }
274
- })
275
-
276
- const session = await stripe.checkout.sessions.create({
277
- customer: user?.stripeCustomerId ?? undefined,
278
- customer_email: !user?.stripeCustomerId ? user?.email : undefined,
279
- mode: 'payment',
280
- payment_method_types: ['card'],
281
- line_items: [{
282
- price_data: {
283
- currency: 'usd',
284
- product_data: { name: description },
285
- unit_amount: amount
286
- },
287
- quantity: 1
288
- }],
289
- success_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success`,
290
- cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/payment/canceled`,
291
- metadata: { userId, ...metadata }
292
- })
293
-
294
- return session
295
- }
296
- ```
297
-
298
- ## When to Use
299
-
300
- - Subscription signup
301
- - One-time purchases
302
- - Upgrade flows
303
- - E-commerce checkout