spaps-mcp 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.
- package/CHANGELOG.md +7 -0
- package/README.md +76 -0
- package/dist/chunk-GPBTYWDD.js +496 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +26 -0
- package/dist/resources/SPAPS_SURFACE_CONTRACT.md +125 -0
- package/dist/resources/glossary.md +36 -0
- package/dist/resources/llms.txt +15 -0
- package/dist/wizard/step-01-setup.md +149 -0
- package/dist/wizard/step-02-environment.md +291 -0
- package/dist/wizard/step-03-sdk-init.md +351 -0
- package/dist/wizard/step-04-email-auth.md +311 -0
- package/dist/wizard/step-05-wallet-auth.md +368 -0
- package/dist/wizard/step-06-magic-link.md +560 -0
- package/dist/wizard/step-07-payments.md +529 -0
- package/dist/wizard/step-08-whitelist.md +338 -0
- package/dist/wizard/step-09-admin.md +579 -0
- package/dist/wizard/step-10-errors.md +525 -0
- package/dist/wizard/step-11-ui-polish.md +640 -0
- package/dist/wizard/step-12-testing.md +588 -0
- package/dist/wizard/wizard.lock +67 -0
- package/package.json +66 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# SPAPS Integration Step 7/12: Stripe Payment Integration
|
|
2
|
+
|
|
3
|
+
## Prerequisites Check
|
|
4
|
+
|
|
5
|
+
Before starting, confirm you have completed:
|
|
6
|
+
- ✅ Step 1-6: Full authentication system working
|
|
7
|
+
- ✅ User can login and tokens are stored
|
|
8
|
+
- ✅ SDK initialized properly
|
|
9
|
+
|
|
10
|
+
## Required TodoWrite List
|
|
11
|
+
|
|
12
|
+
Create a TodoWrite with EXACTLY these items:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
TodoWrite({
|
|
16
|
+
todos: [
|
|
17
|
+
{ content: "Create payments component directory", status: "pending", activeForm: "Creating payments directory" },
|
|
18
|
+
{ content: "Implement listProducts with active filter", status: "pending", activeForm: "Implementing product listing" },
|
|
19
|
+
{ content: "Use createPaymentCheckout for one-time payments", status: "pending", activeForm: "Implementing payment checkout" },
|
|
20
|
+
{ content: "Use createSubscriptionCheckout for subscriptions", status: "pending", activeForm: "Implementing subscription checkout" },
|
|
21
|
+
{ content: "Add createCustomerPortalSession", status: "pending", activeForm: "Adding customer portal" },
|
|
22
|
+
{ content: "Handle checkout URL redirects", status: "pending", activeForm: "Handling redirects" },
|
|
23
|
+
{ content: "Test with amount 999 ($9.99)", status: "pending", activeForm: "Testing payments" },
|
|
24
|
+
{ content: "Request step 8 of SPAPS integration wizard", status: "pending", activeForm: "Requesting next step" }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Critical Method Names
|
|
30
|
+
|
|
31
|
+
⚠️ **EXTREMELY IMPORTANT - CORRECT METHOD NAMES**:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// ✅ CORRECT Methods that exist:
|
|
35
|
+
sdk.payments.createPaymentCheckout() // One-time payment
|
|
36
|
+
sdk.payments.createSubscriptionCheckout() // Subscription
|
|
37
|
+
sdk.payments.listProducts() // Get products
|
|
38
|
+
sdk.payments.createCustomerPortalSession() // Customer portal
|
|
39
|
+
|
|
40
|
+
// ❌ WRONG - These variations DON'T exist:
|
|
41
|
+
sdk.payments.createCheckout() // NO! Too generic
|
|
42
|
+
sdk.payments.createCheckoutSession() // NO! Different name
|
|
43
|
+
sdk.payments.checkout() // NO! Doesn't exist
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Implementation Guide
|
|
47
|
+
|
|
48
|
+
### 1. Payments Component
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// components/payments/stripe-payments.tsx
|
|
52
|
+
'use client'
|
|
53
|
+
|
|
54
|
+
import { useState, useEffect } from 'react'
|
|
55
|
+
import { Button } from "@/components/ui/button"
|
|
56
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
57
|
+
import { Badge } from "@/components/ui/badge"
|
|
58
|
+
import { Separator } from "@/components/ui/separator"
|
|
59
|
+
import { toast } from "sonner"
|
|
60
|
+
import { sdk } from '@/lib/spaps'
|
|
61
|
+
import { SweetPotatoAPIError } from 'spaps-sdk'
|
|
62
|
+
import { CreditCard, ShoppingCart, Settings, Loader2 } from 'lucide-react'
|
|
63
|
+
|
|
64
|
+
interface Product {
|
|
65
|
+
id: string
|
|
66
|
+
name: string
|
|
67
|
+
description?: string
|
|
68
|
+
prices: Array<{
|
|
69
|
+
id: string
|
|
70
|
+
amount: number
|
|
71
|
+
currency: string
|
|
72
|
+
interval?: string // 'month', 'year', etc.
|
|
73
|
+
intervalCount?: number
|
|
74
|
+
}>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function StripePayments() {
|
|
78
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
79
|
+
const [products, setProducts] = useState<Product[]>([])
|
|
80
|
+
const [loadingProducts, setLoadingProducts] = useState(true)
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
loadProducts()
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
const loadProducts = async () => {
|
|
87
|
+
try {
|
|
88
|
+
setLoadingProducts(true)
|
|
89
|
+
|
|
90
|
+
// ✅ CORRECT: List products from Stripe
|
|
91
|
+
const productList = await sdk.payments.listProducts({
|
|
92
|
+
active: true,
|
|
93
|
+
limit: 10
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
setProducts(productList.products || [])
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof SweetPotatoAPIError) {
|
|
99
|
+
toast.error(`Failed to load products: ${error.message}`)
|
|
100
|
+
} else {
|
|
101
|
+
toast.error('Failed to load products')
|
|
102
|
+
}
|
|
103
|
+
console.error('Load products error:', error)
|
|
104
|
+
} finally {
|
|
105
|
+
setLoadingProducts(false)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleOneTimePayment = async () => {
|
|
110
|
+
setIsLoading(true)
|
|
111
|
+
try {
|
|
112
|
+
// ✅ CORRECT: Use createPaymentCheckout for one-time payments
|
|
113
|
+
const checkout = await sdk.payments.createPaymentCheckout({
|
|
114
|
+
product_name: 'Test Product',
|
|
115
|
+
amount: 999, // $9.99 in cents
|
|
116
|
+
currency: 'usd',
|
|
117
|
+
quantity: 1,
|
|
118
|
+
success_url: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
119
|
+
cancel_url: `${window.location.origin}/cancel`,
|
|
120
|
+
metadata: {
|
|
121
|
+
userId: 'user_123',
|
|
122
|
+
source: 'demo_app'
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (checkout.url) {
|
|
127
|
+
// Redirect to Stripe Checkout
|
|
128
|
+
window.location.href = checkout.url
|
|
129
|
+
} else {
|
|
130
|
+
toast.error('Failed to create checkout session')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
handlePaymentError(error)
|
|
135
|
+
} finally {
|
|
136
|
+
setIsLoading(false)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const handleSubscription = async (priceId: string) => {
|
|
141
|
+
setIsLoading(true)
|
|
142
|
+
try {
|
|
143
|
+
// ✅ CORRECT: Use createSubscriptionCheckout for subscriptions
|
|
144
|
+
const checkout = await sdk.payments.createSubscriptionCheckout({
|
|
145
|
+
price_id: priceId,
|
|
146
|
+
success_url: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
147
|
+
cancel_url: `${window.location.origin}/cancel`,
|
|
148
|
+
trial_period_days: 7, // Optional trial
|
|
149
|
+
metadata: {
|
|
150
|
+
userId: 'user_123'
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if (checkout.url) {
|
|
155
|
+
window.location.href = checkout.url
|
|
156
|
+
} else {
|
|
157
|
+
toast.error('Failed to create subscription checkout')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
handlePaymentError(error)
|
|
162
|
+
} finally {
|
|
163
|
+
setIsLoading(false)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const handleCustomerPortal = async () => {
|
|
168
|
+
setIsLoading(true)
|
|
169
|
+
try {
|
|
170
|
+
// ✅ CORRECT: Create customer portal session
|
|
171
|
+
const portal = await sdk.payments.createCustomerPortalSession({
|
|
172
|
+
return_url: window.location.href
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
if (portal.url) {
|
|
176
|
+
window.location.href = portal.url
|
|
177
|
+
} else {
|
|
178
|
+
toast.error('Failed to access customer portal')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
} catch (error) {
|
|
182
|
+
handlePaymentError(error)
|
|
183
|
+
} finally {
|
|
184
|
+
setIsLoading(false)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const handlePaymentError = (error: any) => {
|
|
189
|
+
if (error instanceof SweetPotatoAPIError) {
|
|
190
|
+
switch (error.code) {
|
|
191
|
+
case 'PAYMENT_REQUIRED':
|
|
192
|
+
toast.error('Payment required to continue')
|
|
193
|
+
break
|
|
194
|
+
case 'INSUFFICIENT_FUNDS':
|
|
195
|
+
toast.error('Payment failed: Insufficient funds')
|
|
196
|
+
break
|
|
197
|
+
case 'CARD_DECLINED':
|
|
198
|
+
toast.error('Card was declined')
|
|
199
|
+
break
|
|
200
|
+
default:
|
|
201
|
+
toast.error(error.message || 'Payment failed')
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
toast.error('An unexpected error occurred')
|
|
205
|
+
}
|
|
206
|
+
console.error('Payment error:', error)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const formatPrice = (amount: number, currency: string) => {
|
|
210
|
+
return new Intl.NumberFormat('en-US', {
|
|
211
|
+
style: 'currency',
|
|
212
|
+
currency: currency.toUpperCase(),
|
|
213
|
+
}).format(amount / 100)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div className="space-y-6">
|
|
218
|
+
{/* One-Time Payment */}
|
|
219
|
+
<Card>
|
|
220
|
+
<CardHeader>
|
|
221
|
+
<CardTitle className="flex items-center gap-2">
|
|
222
|
+
<CreditCard className="h-5 w-5" />
|
|
223
|
+
One-Time Payment
|
|
224
|
+
</CardTitle>
|
|
225
|
+
<CardDescription>
|
|
226
|
+
Test a single payment with Stripe Checkout
|
|
227
|
+
</CardDescription>
|
|
228
|
+
</CardHeader>
|
|
229
|
+
<CardContent className="space-y-4">
|
|
230
|
+
<div className="p-4 bg-muted rounded-lg">
|
|
231
|
+
<p className="text-sm font-medium">Test Product</p>
|
|
232
|
+
<p className="text-2xl font-bold">$9.99</p>
|
|
233
|
+
<p className="text-xs text-muted-foreground">One-time payment</p>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<Button
|
|
237
|
+
onClick={handleOneTimePayment}
|
|
238
|
+
className="w-full"
|
|
239
|
+
disabled={isLoading}
|
|
240
|
+
>
|
|
241
|
+
{isLoading ? (
|
|
242
|
+
<>
|
|
243
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
244
|
+
Processing...
|
|
245
|
+
</>
|
|
246
|
+
) : (
|
|
247
|
+
<>
|
|
248
|
+
<ShoppingCart className="mr-2 h-4 w-4" />
|
|
249
|
+
Pay $9.99 Now
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
</Button>
|
|
253
|
+
|
|
254
|
+
<p className="text-xs text-muted-foreground text-center">
|
|
255
|
+
Test card: 4242 4242 4242 4242
|
|
256
|
+
</p>
|
|
257
|
+
</CardContent>
|
|
258
|
+
</Card>
|
|
259
|
+
|
|
260
|
+
{/* Products List */}
|
|
261
|
+
<Card>
|
|
262
|
+
<CardHeader>
|
|
263
|
+
<CardTitle>Available Products</CardTitle>
|
|
264
|
+
<CardDescription>
|
|
265
|
+
Products and subscriptions from your Stripe account
|
|
266
|
+
</CardDescription>
|
|
267
|
+
</CardHeader>
|
|
268
|
+
<CardContent>
|
|
269
|
+
{loadingProducts ? (
|
|
270
|
+
<div className="flex justify-center py-8">
|
|
271
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
272
|
+
</div>
|
|
273
|
+
) : products.length === 0 ? (
|
|
274
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
275
|
+
No products available
|
|
276
|
+
</div>
|
|
277
|
+
) : (
|
|
278
|
+
<div className="space-y-4">
|
|
279
|
+
{products.map((product) => (
|
|
280
|
+
<div key={product.id} className="border rounded-lg p-4">
|
|
281
|
+
<h4 className="font-medium">{product.name}</h4>
|
|
282
|
+
{product.description && (
|
|
283
|
+
<p className="text-sm text-muted-foreground mb-2">
|
|
284
|
+
{product.description}
|
|
285
|
+
</p>
|
|
286
|
+
)}
|
|
287
|
+
<div className="space-y-2">
|
|
288
|
+
{product.prices.map((price) => (
|
|
289
|
+
<div key={price.id} className="flex items-center justify-between">
|
|
290
|
+
<div>
|
|
291
|
+
<span className="text-sm font-medium">
|
|
292
|
+
{formatPrice(price.amount, price.currency)}
|
|
293
|
+
</span>
|
|
294
|
+
{price.interval && (
|
|
295
|
+
<span className="text-xs text-muted-foreground ml-1">
|
|
296
|
+
/ {price.interval}
|
|
297
|
+
</span>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
<Button
|
|
301
|
+
size="sm"
|
|
302
|
+
onClick={() =>
|
|
303
|
+
price.interval
|
|
304
|
+
? handleSubscription(price.id)
|
|
305
|
+
: handleOneTimePayment()
|
|
306
|
+
}
|
|
307
|
+
disabled={isLoading}
|
|
308
|
+
>
|
|
309
|
+
{price.interval ? 'Subscribe' : 'Buy Now'}
|
|
310
|
+
</Button>
|
|
311
|
+
</div>
|
|
312
|
+
))}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
))}
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
318
|
+
</CardContent>
|
|
319
|
+
</Card>
|
|
320
|
+
|
|
321
|
+
{/* Customer Portal */}
|
|
322
|
+
<Card>
|
|
323
|
+
<CardHeader>
|
|
324
|
+
<CardTitle className="flex items-center gap-2">
|
|
325
|
+
<Settings className="h-5 w-5" />
|
|
326
|
+
Customer Portal
|
|
327
|
+
</CardTitle>
|
|
328
|
+
<CardDescription>
|
|
329
|
+
Manage your subscriptions and billing
|
|
330
|
+
</CardDescription>
|
|
331
|
+
</CardHeader>
|
|
332
|
+
<CardContent>
|
|
333
|
+
<Button
|
|
334
|
+
onClick={handleCustomerPortal}
|
|
335
|
+
className="w-full"
|
|
336
|
+
disabled={isLoading}
|
|
337
|
+
variant="outline"
|
|
338
|
+
>
|
|
339
|
+
Open Customer Portal
|
|
340
|
+
</Button>
|
|
341
|
+
<p className="text-xs text-muted-foreground text-center mt-2">
|
|
342
|
+
View invoices, update payment methods, cancel subscriptions
|
|
343
|
+
</p>
|
|
344
|
+
</CardContent>
|
|
345
|
+
</Card>
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 2. Success/Cancel Pages
|
|
352
|
+
|
|
353
|
+
Create pages to handle checkout redirects:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// app/success/page.tsx
|
|
357
|
+
'use client'
|
|
358
|
+
|
|
359
|
+
import { useEffect } from 'react'
|
|
360
|
+
import { useSearchParams } from 'next/navigation'
|
|
361
|
+
import { Card } from "@/components/ui/card"
|
|
362
|
+
import { Button } from "@/components/ui/button"
|
|
363
|
+
import Link from 'next/link'
|
|
364
|
+
import { CheckCircle } from 'lucide-react'
|
|
365
|
+
|
|
366
|
+
export default function PaymentSuccess() {
|
|
367
|
+
const searchParams = useSearchParams()
|
|
368
|
+
const sessionId = searchParams.get('session_id')
|
|
369
|
+
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
// You could verify the session here if needed
|
|
372
|
+
if (sessionId) {
|
|
373
|
+
console.log('Payment successful! Session:', sessionId)
|
|
374
|
+
}
|
|
375
|
+
}, [sessionId])
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<div className="min-h-screen flex items-center justify-center p-4">
|
|
379
|
+
<Card className="w-[400px] p-8">
|
|
380
|
+
<div className="text-center space-y-4">
|
|
381
|
+
<CheckCircle className="h-16 w-16 text-green-600 mx-auto" />
|
|
382
|
+
<h1 className="text-2xl font-bold">Payment Successful!</h1>
|
|
383
|
+
<p className="text-muted-foreground">
|
|
384
|
+
Your payment has been processed successfully.
|
|
385
|
+
</p>
|
|
386
|
+
{sessionId && (
|
|
387
|
+
<p className="text-xs text-muted-foreground font-mono">
|
|
388
|
+
Session: {sessionId.substring(0, 20)}...
|
|
389
|
+
</p>
|
|
390
|
+
)}
|
|
391
|
+
<Button asChild className="w-full">
|
|
392
|
+
<Link href="/dashboard">
|
|
393
|
+
Back to Dashboard
|
|
394
|
+
</Link>
|
|
395
|
+
</Button>
|
|
396
|
+
</div>
|
|
397
|
+
</Card>
|
|
398
|
+
</div>
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Method Details
|
|
404
|
+
|
|
405
|
+
### createPaymentCheckout
|
|
406
|
+
|
|
407
|
+
For one-time payments:
|
|
408
|
+
```typescript
|
|
409
|
+
sdk.payments.createPaymentCheckout({
|
|
410
|
+
product_name?: string, // Product name
|
|
411
|
+
amount?: number, // Amount in cents
|
|
412
|
+
currency?: string, // Currency code (usd, eur, etc.)
|
|
413
|
+
quantity?: number, // Quantity
|
|
414
|
+
price_id?: string, // Or use existing price ID
|
|
415
|
+
success_url: string, // Where to redirect on success
|
|
416
|
+
cancel_url: string, // Where to redirect on cancel
|
|
417
|
+
metadata?: Record<string, string> // Custom metadata
|
|
418
|
+
})
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### createSubscriptionCheckout
|
|
422
|
+
|
|
423
|
+
For recurring payments:
|
|
424
|
+
```typescript
|
|
425
|
+
sdk.payments.createSubscriptionCheckout({
|
|
426
|
+
price_id: string, // Stripe price ID (required)
|
|
427
|
+
success_url: string, // Where to redirect on success
|
|
428
|
+
cancel_url: string, // Where to redirect on cancel
|
|
429
|
+
trial_period_days?: number, // Optional trial
|
|
430
|
+
metadata?: Record<string, string> // Custom metadata
|
|
431
|
+
})
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Validation Checklist
|
|
435
|
+
|
|
436
|
+
✅ **Method Names**:
|
|
437
|
+
- Uses `createPaymentCheckout` for one-time
|
|
438
|
+
- Uses `createSubscriptionCheckout` for recurring
|
|
439
|
+
- NOT using generic `createCheckout`
|
|
440
|
+
|
|
441
|
+
✅ **Implementation**:
|
|
442
|
+
- Products load from Stripe
|
|
443
|
+
- Checkout redirects work
|
|
444
|
+
- Success/cancel pages exist
|
|
445
|
+
- Customer portal accessible
|
|
446
|
+
|
|
447
|
+
✅ **Testing**:
|
|
448
|
+
- Test payment with $9.99 (999 cents)
|
|
449
|
+
- Use test card: 4242 4242 4242 4242
|
|
450
|
+
- Any future expiry, any CVC
|
|
451
|
+
- Should redirect to Stripe
|
|
452
|
+
|
|
453
|
+
## Common Mistakes to Avoid
|
|
454
|
+
|
|
455
|
+
❌ **Wrong method names**:
|
|
456
|
+
```typescript
|
|
457
|
+
// WRONG - These don't exist
|
|
458
|
+
sdk.payments.createCheckout()
|
|
459
|
+
sdk.payments.checkout()
|
|
460
|
+
sdk.payments.createSession()
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
❌ **Direct Stripe integration**:
|
|
464
|
+
```typescript
|
|
465
|
+
// WRONG - Use SDK instead
|
|
466
|
+
import Stripe from 'stripe'
|
|
467
|
+
const stripe = new Stripe(...)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
❌ **Wrong amount format**:
|
|
471
|
+
```typescript
|
|
472
|
+
// WRONG - Amount should be in cents
|
|
473
|
+
amount: 9.99 // Should be 999
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
❌ **Missing redirects**:
|
|
477
|
+
```typescript
|
|
478
|
+
// WRONG - Must include both URLs
|
|
479
|
+
createPaymentCheckout({
|
|
480
|
+
amount: 999
|
|
481
|
+
// Missing success_url and cancel_url!
|
|
482
|
+
})
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Testing Stripe Integration
|
|
486
|
+
|
|
487
|
+
### Test Cards
|
|
488
|
+
|
|
489
|
+
- **Success**: 4242 4242 4242 4242
|
|
490
|
+
- **Decline**: 4000 0000 0000 0002
|
|
491
|
+
- **Requires Auth**: 4000 0025 0000 3155
|
|
492
|
+
|
|
493
|
+
### Local Testing
|
|
494
|
+
|
|
495
|
+
In local mode:
|
|
496
|
+
1. Uses Stripe test mode automatically
|
|
497
|
+
2. No real charges occur
|
|
498
|
+
3. Can use test cards
|
|
499
|
+
4. Webhooks handled by SPAPS
|
|
500
|
+
|
|
501
|
+
### Verification
|
|
502
|
+
|
|
503
|
+
After payment:
|
|
504
|
+
1. Check Stripe Dashboard (test mode)
|
|
505
|
+
2. Verify session completed
|
|
506
|
+
3. Check user's payment status
|
|
507
|
+
4. Test customer portal access
|
|
508
|
+
|
|
509
|
+
## Next Step
|
|
510
|
+
|
|
511
|
+
When ALL todos are ✅ complete and payments work:
|
|
512
|
+
|
|
513
|
+
```javascript
|
|
514
|
+
mcp__product-manager__get_agent_instructions({
|
|
515
|
+
category: "spaps-integration",
|
|
516
|
+
project: "spaps-demo",
|
|
517
|
+
step: 8
|
|
518
|
+
})
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Summary
|
|
522
|
+
|
|
523
|
+
Stripe integration via SDK provides:
|
|
524
|
+
- **Simple checkout** - Just redirect to Stripe
|
|
525
|
+
- **Secure payments** - PCI compliance handled
|
|
526
|
+
- **Subscriptions** - Recurring payments supported
|
|
527
|
+
- **Customer portal** - Self-service management
|
|
528
|
+
|
|
529
|
+
Remember: Use the EXACT method names - this is where most errors occur!
|