specweave 0.24.0 → 0.24.6

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 (42) hide show
  1. package/.claude-plugin/marketplace.json +55 -0
  2. package/CLAUDE.md +42 -0
  3. package/dist/src/cli/commands/init.d.ts.map +1 -1
  4. package/dist/src/cli/commands/init.js +80 -41
  5. package/dist/src/cli/commands/init.js.map +1 -1
  6. package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -1
  7. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  8. package/dist/src/config/types.d.ts +24 -24
  9. package/dist/src/core/config/types.d.ts +25 -0
  10. package/dist/src/core/config/types.d.ts.map +1 -1
  11. package/dist/src/core/config/types.js +6 -0
  12. package/dist/src/core/config/types.js.map +1 -1
  13. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +33 -0
  14. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -0
  15. package/dist/src/core/repo-structure/repo-bulk-discovery.js +275 -0
  16. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -0
  17. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +9 -0
  18. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  19. package/dist/src/core/repo-structure/repo-structure-manager.js +255 -87
  20. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  21. package/dist/src/init/architecture/types.d.ts +6 -6
  22. package/dist/src/utils/plugin-validator.d.ts.map +1 -1
  23. package/dist/src/utils/plugin-validator.js +15 -14
  24. package/dist/src/utils/plugin-validator.js.map +1 -1
  25. package/package.json +4 -4
  26. package/plugins/specweave/.claude-plugin/plugin.json +4 -4
  27. package/plugins/specweave/agents/pm/AGENT.md +2 -0
  28. package/plugins/specweave/commands/specweave-do.md +0 -47
  29. package/plugins/specweave/commands/specweave-increment.md +0 -82
  30. package/plugins/specweave/commands/specweave-next.md +0 -47
  31. package/plugins/specweave/hooks/post-task-completion.sh +67 -6
  32. package/plugins/specweave/hooks/pre-edit-spec.sh +11 -0
  33. package/plugins/specweave/hooks/pre-task-completion.sh +69 -2
  34. package/plugins/specweave/hooks/pre-write-spec.sh +11 -0
  35. package/plugins/specweave/skills/increment-planner/SKILL.md +124 -4
  36. package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +21 -0
  37. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +150 -0
  38. package/plugins/specweave-payments/commands/stripe-setup.md +931 -0
  39. package/plugins/specweave-payments/commands/subscription-flow.md +1193 -0
  40. package/plugins/specweave-payments/commands/subscription-manage.md +386 -0
  41. package/plugins/specweave-payments/commands/webhook-setup.md +295 -0
  42. package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +21 -0
@@ -0,0 +1,931 @@
1
+ # /specweave-payments:stripe-setup
2
+
3
+ Complete Stripe integration setup guide with production-ready code templates, security best practices, and testing workflows.
4
+
5
+ You are a payment integration expert who implements secure, PCI-compliant Stripe payment systems.
6
+
7
+ ## Your Task
8
+
9
+ Set up complete Stripe payment integration with checkout flows, webhook handling, subscription billing, and customer management.
10
+
11
+ ### 1. Environment Setup
12
+
13
+ **Install Dependencies**:
14
+
15
+ ```bash
16
+ # Node.js
17
+ npm install stripe @stripe/stripe-js dotenv
18
+
19
+ # Python
20
+ pip install stripe python-dotenv
21
+
22
+ # Ruby
23
+ gem install stripe dotenv
24
+
25
+ # PHP
26
+ composer require stripe/stripe-php vlucas/phpdotenv
27
+ ```
28
+
29
+ **Environment Variables**:
30
+
31
+ ```bash
32
+ # .env (NEVER commit this file!)
33
+ # Get keys from https://dashboard.stripe.com/apikeys
34
+
35
+ # Test mode (development)
36
+ STRIPE_PUBLISHABLE_KEY=pk_test_51...
37
+ STRIPE_SECRET_KEY=sk_test_51...
38
+ STRIPE_WEBHOOK_SECRET=whsec_...
39
+
40
+ # Live mode (production)
41
+ # STRIPE_PUBLISHABLE_KEY=pk_live_51...
42
+ # STRIPE_SECRET_KEY=sk_live_51...
43
+ # STRIPE_WEBHOOK_SECRET=whsec_...
44
+
45
+ # App configuration
46
+ STRIPE_SUCCESS_URL=https://yourdomain.com/success
47
+ STRIPE_CANCEL_URL=https://yourdomain.com/cancel
48
+ STRIPE_CURRENCY=usd
49
+ ```
50
+
51
+ ### 2. Backend Setup (Node.js/Express)
52
+
53
+ **Stripe Client Initialization**:
54
+
55
+ ```typescript
56
+ // src/config/stripe.ts
57
+ import Stripe from 'stripe';
58
+ import dotenv from 'dotenv';
59
+
60
+ dotenv.config();
61
+
62
+ if (!process.env.STRIPE_SECRET_KEY) {
63
+ throw new Error('STRIPE_SECRET_KEY is not set in environment variables');
64
+ }
65
+
66
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
67
+ apiVersion: '2023-10-16',
68
+ typescript: true,
69
+ maxNetworkRetries: 2,
70
+ timeout: 10000, // 10 seconds
71
+ });
72
+
73
+ export const STRIPE_CONFIG = {
74
+ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!,
75
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
76
+ successUrl: process.env.STRIPE_SUCCESS_URL || 'http://localhost:3000/success',
77
+ cancelUrl: process.env.STRIPE_CANCEL_URL || 'http://localhost:3000/cancel',
78
+ currency: process.env.STRIPE_CURRENCY || 'usd',
79
+ };
80
+ ```
81
+
82
+ **Payment Service**:
83
+
84
+ ```typescript
85
+ // src/services/payment.service.ts
86
+ import { stripe } from '../config/stripe';
87
+ import type Stripe from 'stripe';
88
+
89
+ export class PaymentService {
90
+ /**
91
+ * Create a one-time payment checkout session
92
+ */
93
+ async createCheckoutSession(params: {
94
+ amount: number;
95
+ currency?: string;
96
+ customerId?: string;
97
+ metadata?: Record<string, string>;
98
+ }): Promise<Stripe.Checkout.Session> {
99
+ try {
100
+ const session = await stripe.checkout.sessions.create({
101
+ payment_method_types: ['card'],
102
+ line_items: [
103
+ {
104
+ price_data: {
105
+ currency: params.currency || 'usd',
106
+ product_data: {
107
+ name: 'Payment',
108
+ description: 'One-time payment',
109
+ },
110
+ unit_amount: params.amount, // Amount in cents
111
+ },
112
+ quantity: 1,
113
+ },
114
+ ],
115
+ mode: 'payment',
116
+ success_url: `${process.env.STRIPE_SUCCESS_URL}?session_id={CHECKOUT_SESSION_ID}`,
117
+ cancel_url: process.env.STRIPE_CANCEL_URL,
118
+ customer: params.customerId,
119
+ metadata: params.metadata,
120
+ // Enable automatic tax calculation (optional)
121
+ automatic_tax: { enabled: false },
122
+ // Customer email collection
123
+ customer_email: params.customerId ? undefined : '',
124
+ });
125
+
126
+ return session;
127
+ } catch (error) {
128
+ console.error('Failed to create checkout session:', error);
129
+ throw new Error('Payment session creation failed');
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Create a payment intent for custom checkout UI
135
+ */
136
+ async createPaymentIntent(params: {
137
+ amount: number;
138
+ currency?: string;
139
+ customerId?: string;
140
+ paymentMethodTypes?: string[];
141
+ metadata?: Record<string, string>;
142
+ }): Promise<Stripe.PaymentIntent> {
143
+ try {
144
+ const paymentIntent = await stripe.paymentIntents.create({
145
+ amount: params.amount,
146
+ currency: params.currency || 'usd',
147
+ customer: params.customerId,
148
+ payment_method_types: params.paymentMethodTypes || ['card'],
149
+ metadata: params.metadata,
150
+ // Automatic payment methods (enables more payment methods)
151
+ automatic_payment_methods: {
152
+ enabled: true,
153
+ allow_redirects: 'never', // or 'always' for redirect-based methods
154
+ },
155
+ });
156
+
157
+ return paymentIntent;
158
+ } catch (error) {
159
+ console.error('Failed to create payment intent:', error);
160
+ throw new Error('Payment intent creation failed');
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Retrieve a payment intent
166
+ */
167
+ async getPaymentIntent(paymentIntentId: string): Promise<Stripe.PaymentIntent> {
168
+ try {
169
+ return await stripe.paymentIntents.retrieve(paymentIntentId);
170
+ } catch (error) {
171
+ console.error('Failed to retrieve payment intent:', error);
172
+ throw new Error('Payment intent retrieval failed');
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Confirm a payment intent (server-side confirmation)
178
+ */
179
+ async confirmPaymentIntent(
180
+ paymentIntentId: string,
181
+ paymentMethodId: string
182
+ ): Promise<Stripe.PaymentIntent> {
183
+ try {
184
+ return await stripe.paymentIntents.confirm(paymentIntentId, {
185
+ payment_method: paymentMethodId,
186
+ });
187
+ } catch (error) {
188
+ console.error('Failed to confirm payment intent:', error);
189
+ throw new Error('Payment confirmation failed');
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Create or update a customer
195
+ */
196
+ async createCustomer(params: {
197
+ email: string;
198
+ name?: string;
199
+ phone?: string;
200
+ metadata?: Record<string, string>;
201
+ paymentMethodId?: string;
202
+ }): Promise<Stripe.Customer> {
203
+ try {
204
+ const customer = await stripe.customers.create({
205
+ email: params.email,
206
+ name: params.name,
207
+ phone: params.phone,
208
+ metadata: params.metadata,
209
+ payment_method: params.paymentMethodId,
210
+ invoice_settings: params.paymentMethodId
211
+ ? {
212
+ default_payment_method: params.paymentMethodId,
213
+ }
214
+ : undefined,
215
+ });
216
+
217
+ return customer;
218
+ } catch (error) {
219
+ console.error('Failed to create customer:', error);
220
+ throw new Error('Customer creation failed');
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Attach a payment method to a customer
226
+ */
227
+ async attachPaymentMethod(
228
+ paymentMethodId: string,
229
+ customerId: string,
230
+ setAsDefault = true
231
+ ): Promise<Stripe.PaymentMethod> {
232
+ try {
233
+ // Attach payment method
234
+ const paymentMethod = await stripe.paymentMethods.attach(paymentMethodId, {
235
+ customer: customerId,
236
+ });
237
+
238
+ // Set as default if requested
239
+ if (setAsDefault) {
240
+ await stripe.customers.update(customerId, {
241
+ invoice_settings: {
242
+ default_payment_method: paymentMethodId,
243
+ },
244
+ });
245
+ }
246
+
247
+ return paymentMethod;
248
+ } catch (error) {
249
+ console.error('Failed to attach payment method:', error);
250
+ throw new Error('Payment method attachment failed');
251
+ }
252
+ }
253
+
254
+ /**
255
+ * List customer payment methods
256
+ */
257
+ async listPaymentMethods(customerId: string): Promise<Stripe.PaymentMethod[]> {
258
+ try {
259
+ const paymentMethods = await stripe.paymentMethods.list({
260
+ customer: customerId,
261
+ type: 'card',
262
+ });
263
+
264
+ return paymentMethods.data;
265
+ } catch (error) {
266
+ console.error('Failed to list payment methods:', error);
267
+ throw new Error('Payment method listing failed');
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Create a refund
273
+ */
274
+ async createRefund(params: {
275
+ paymentIntentId?: string;
276
+ chargeId?: string;
277
+ amount?: number; // Partial refund amount in cents
278
+ reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer';
279
+ metadata?: Record<string, string>;
280
+ }): Promise<Stripe.Refund> {
281
+ try {
282
+ const refund = await stripe.refunds.create({
283
+ payment_intent: params.paymentIntentId,
284
+ charge: params.chargeId,
285
+ amount: params.amount,
286
+ reason: params.reason,
287
+ metadata: params.metadata,
288
+ });
289
+
290
+ return refund;
291
+ } catch (error) {
292
+ console.error('Failed to create refund:', error);
293
+ throw new Error('Refund creation failed');
294
+ }
295
+ }
296
+ }
297
+
298
+ export const paymentService = new PaymentService();
299
+ ```
300
+
301
+ **Express API Routes**:
302
+
303
+ ```typescript
304
+ // src/routes/payment.routes.ts
305
+ import { Router, Request, Response } from 'express';
306
+ import { paymentService } from '../services/payment.service';
307
+
308
+ const router = Router();
309
+
310
+ /**
311
+ * POST /api/payments/checkout
312
+ * Create a checkout session
313
+ */
314
+ router.post('/checkout', async (req: Request, res: Response) => {
315
+ try {
316
+ const { amount, currency, customerId, metadata } = req.body;
317
+
318
+ // Validate amount
319
+ if (!amount || amount <= 0) {
320
+ return res.status(400).json({ error: 'Invalid amount' });
321
+ }
322
+
323
+ const session = await paymentService.createCheckoutSession({
324
+ amount,
325
+ currency,
326
+ customerId,
327
+ metadata,
328
+ });
329
+
330
+ res.json({ sessionId: session.id, url: session.url });
331
+ } catch (error) {
332
+ console.error('Checkout error:', error);
333
+ res.status(500).json({ error: 'Failed to create checkout session' });
334
+ }
335
+ });
336
+
337
+ /**
338
+ * POST /api/payments/intent
339
+ * Create a payment intent for custom UI
340
+ */
341
+ router.post('/intent', async (req: Request, res: Response) => {
342
+ try {
343
+ const { amount, currency, customerId, metadata } = req.body;
344
+
345
+ if (!amount || amount <= 0) {
346
+ return res.status(400).json({ error: 'Invalid amount' });
347
+ }
348
+
349
+ const paymentIntent = await paymentService.createPaymentIntent({
350
+ amount,
351
+ currency,
352
+ customerId,
353
+ metadata,
354
+ });
355
+
356
+ res.json({ clientSecret: paymentIntent.client_secret });
357
+ } catch (error) {
358
+ console.error('Payment intent error:', error);
359
+ res.status(500).json({ error: 'Failed to create payment intent' });
360
+ }
361
+ });
362
+
363
+ /**
364
+ * POST /api/payments/customers
365
+ * Create a customer
366
+ */
367
+ router.post('/customers', async (req: Request, res: Response) => {
368
+ try {
369
+ const { email, name, phone, metadata } = req.body;
370
+
371
+ if (!email) {
372
+ return res.status(400).json({ error: 'Email is required' });
373
+ }
374
+
375
+ const customer = await paymentService.createCustomer({
376
+ email,
377
+ name,
378
+ phone,
379
+ metadata,
380
+ });
381
+
382
+ res.json({ customerId: customer.id });
383
+ } catch (error) {
384
+ console.error('Customer creation error:', error);
385
+ res.status(500).json({ error: 'Failed to create customer' });
386
+ }
387
+ });
388
+
389
+ /**
390
+ * POST /api/payments/refunds
391
+ * Create a refund
392
+ */
393
+ router.post('/refunds', async (req: Request, res: Response) => {
394
+ try {
395
+ const { paymentIntentId, amount, reason, metadata } = req.body;
396
+
397
+ if (!paymentIntentId) {
398
+ return res.status(400).json({ error: 'Payment Intent ID is required' });
399
+ }
400
+
401
+ const refund = await paymentService.createRefund({
402
+ paymentIntentId,
403
+ amount,
404
+ reason,
405
+ metadata,
406
+ });
407
+
408
+ res.json({ refundId: refund.id, status: refund.status });
409
+ } catch (error) {
410
+ console.error('Refund error:', error);
411
+ res.status(500).json({ error: 'Failed to create refund' });
412
+ }
413
+ });
414
+
415
+ /**
416
+ * GET /api/payments/config
417
+ * Get public Stripe configuration
418
+ */
419
+ router.get('/config', (req: Request, res: Response) => {
420
+ res.json({
421
+ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
422
+ });
423
+ });
424
+
425
+ export default router;
426
+ ```
427
+
428
+ ### 3. Frontend Setup (React)
429
+
430
+ **Stripe Provider**:
431
+
432
+ ```typescript
433
+ // src/providers/StripeProvider.tsx
434
+ import React from 'react';
435
+ import { Elements } from '@stripe/react-stripe-js';
436
+ import { loadStripe, Stripe } from '@stripe/stripe-js';
437
+
438
+ // Load Stripe.js outside of component to avoid recreating the instance
439
+ let stripePromise: Promise<Stripe | null>;
440
+
441
+ const getStripe = () => {
442
+ if (!stripePromise) {
443
+ const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '';
444
+ stripePromise = loadStripe(publishableKey);
445
+ }
446
+ return stripePromise;
447
+ };
448
+
449
+ interface StripeProviderProps {
450
+ children: React.ReactNode;
451
+ }
452
+
453
+ export const StripeProvider: React.FC<StripeProviderProps> = ({ children }) => {
454
+ return (
455
+ <Elements stripe={getStripe()}>
456
+ {children}
457
+ </Elements>
458
+ );
459
+ };
460
+ ```
461
+
462
+ **Payment Form Component**:
463
+
464
+ ```typescript
465
+ // src/components/PaymentForm.tsx
466
+ import React, { useState } from 'react';
467
+ import {
468
+ useStripe,
469
+ useElements,
470
+ CardElement,
471
+ PaymentElement,
472
+ } from '@stripe/react-stripe-js';
473
+ import type { StripeError } from '@stripe/stripe-js';
474
+
475
+ interface PaymentFormProps {
476
+ amount: number;
477
+ currency?: string;
478
+ onSuccess: (paymentIntentId: string) => void;
479
+ onError: (error: string) => void;
480
+ customerId?: string;
481
+ metadata?: Record<string, string>;
482
+ }
483
+
484
+ export const PaymentForm: React.FC<PaymentFormProps> = ({
485
+ amount,
486
+ currency = 'usd',
487
+ onSuccess,
488
+ onError,
489
+ customerId,
490
+ metadata,
491
+ }) => {
492
+ const stripe = useStripe();
493
+ const elements = useElements();
494
+ const [loading, setLoading] = useState(false);
495
+ const [error, setError] = useState<string | null>(null);
496
+
497
+ const handleSubmit = async (event: React.FormEvent) => {
498
+ event.preventDefault();
499
+
500
+ if (!stripe || !elements) {
501
+ // Stripe.js hasn't loaded yet
502
+ return;
503
+ }
504
+
505
+ setLoading(true);
506
+ setError(null);
507
+
508
+ try {
509
+ // Create payment intent on backend
510
+ const response = await fetch('/api/payments/intent', {
511
+ method: 'POST',
512
+ headers: { 'Content-Type': 'application/json' },
513
+ body: JSON.stringify({
514
+ amount,
515
+ currency,
516
+ customerId,
517
+ metadata,
518
+ }),
519
+ });
520
+
521
+ const { clientSecret } = await response.json();
522
+
523
+ // Confirm payment with Stripe.js
524
+ const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(
525
+ clientSecret,
526
+ {
527
+ payment_method: {
528
+ card: elements.getElement(CardElement)!,
529
+ billing_details: {
530
+ // Add billing details if collected
531
+ },
532
+ },
533
+ }
534
+ );
535
+
536
+ if (stripeError) {
537
+ setError(stripeError.message || 'Payment failed');
538
+ onError(stripeError.message || 'Payment failed');
539
+ } else if (paymentIntent && paymentIntent.status === 'succeeded') {
540
+ onSuccess(paymentIntent.id);
541
+ }
542
+ } catch (err) {
543
+ const errorMessage = err instanceof Error ? err.message : 'Payment failed';
544
+ setError(errorMessage);
545
+ onError(errorMessage);
546
+ } finally {
547
+ setLoading(false);
548
+ }
549
+ };
550
+
551
+ return (
552
+ <form onSubmit={handleSubmit} className="max-w-md mx-auto p-6">
553
+ <div className="mb-6">
554
+ <label className="block text-sm font-medium text-gray-700 mb-2">
555
+ Card Details
556
+ </label>
557
+ <div className="border border-gray-300 rounded-lg p-3">
558
+ <CardElement
559
+ options={{
560
+ style: {
561
+ base: {
562
+ fontSize: '16px',
563
+ color: '#424770',
564
+ '::placeholder': {
565
+ color: '#aab7c4',
566
+ },
567
+ },
568
+ invalid: {
569
+ color: '#9e2146',
570
+ },
571
+ },
572
+ }}
573
+ />
574
+ </div>
575
+ </div>
576
+
577
+ {error && (
578
+ <div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
579
+ {error}
580
+ </div>
581
+ )}
582
+
583
+ <button
584
+ type="submit"
585
+ disabled={!stripe || loading}
586
+ className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
587
+ >
588
+ {loading ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`}
589
+ </button>
590
+ </form>
591
+ );
592
+ };
593
+ ```
594
+
595
+ **Checkout Session Flow**:
596
+
597
+ ```typescript
598
+ // src/components/CheckoutButton.tsx
599
+ import React, { useState } from 'react';
600
+ import { loadStripe } from '@stripe/stripe-js';
601
+
602
+ const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
603
+
604
+ interface CheckoutButtonProps {
605
+ amount: number;
606
+ currency?: string;
607
+ buttonText?: string;
608
+ }
609
+
610
+ export const CheckoutButton: React.FC<CheckoutButtonProps> = ({
611
+ amount,
612
+ currency = 'usd',
613
+ buttonText = 'Checkout',
614
+ }) => {
615
+ const [loading, setLoading] = useState(false);
616
+
617
+ const handleCheckout = async () => {
618
+ setLoading(true);
619
+
620
+ try {
621
+ // Create checkout session
622
+ const response = await fetch('/api/payments/checkout', {
623
+ method: 'POST',
624
+ headers: { 'Content-Type': 'application/json' },
625
+ body: JSON.stringify({ amount, currency }),
626
+ });
627
+
628
+ const { sessionId } = await response.json();
629
+
630
+ // Redirect to Stripe Checkout
631
+ const stripe = await stripePromise;
632
+ if (stripe) {
633
+ const { error } = await stripe.redirectToCheckout({ sessionId });
634
+ if (error) {
635
+ console.error('Checkout error:', error);
636
+ }
637
+ }
638
+ } catch (error) {
639
+ console.error('Checkout error:', error);
640
+ } finally {
641
+ setLoading(false);
642
+ }
643
+ };
644
+
645
+ return (
646
+ <button
647
+ onClick={handleCheckout}
648
+ disabled={loading}
649
+ className="bg-blue-600 text-white py-2 px-6 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
650
+ >
651
+ {loading ? 'Loading...' : buttonText}
652
+ </button>
653
+ );
654
+ };
655
+ ```
656
+
657
+ ### 4. Testing
658
+
659
+ **Test Cards**:
660
+
661
+ ```typescript
662
+ // Test card numbers for different scenarios
663
+ export const TEST_CARDS = {
664
+ // Success
665
+ VISA_SUCCESS: '4242424242424242',
666
+ VISA_DEBIT: '4000056655665556',
667
+ MASTERCARD: '5555555555554444',
668
+
669
+ // Authentication required
670
+ THREE_D_SECURE: '4000002500003155',
671
+
672
+ // Failure scenarios
673
+ CARD_DECLINED: '4000000000000002',
674
+ INSUFFICIENT_FUNDS: '4000000000009995',
675
+ LOST_CARD: '4000000000009987',
676
+ STOLEN_CARD: '4000000000009979',
677
+ EXPIRED_CARD: '4000000000000069',
678
+ INCORRECT_CVC: '4000000000000127',
679
+ PROCESSING_ERROR: '4000000000000119',
680
+
681
+ // Special cases
682
+ DISPUTE: '4000000000000259',
683
+ FRAUD: '4100000000000019',
684
+ };
685
+
686
+ // Any future expiry date (e.g., 12/34)
687
+ // Any 3-digit CVC
688
+ // Any postal code
689
+ ```
690
+
691
+ **Integration Test**:
692
+
693
+ ```typescript
694
+ // tests/integration/payment.test.ts
695
+ import { paymentService } from '../../src/services/payment.service';
696
+ import Stripe from 'stripe';
697
+
698
+ describe('Payment Service Integration', () => {
699
+ describe('Payment Intent', () => {
700
+ it('should create a payment intent', async () => {
701
+ const paymentIntent = await paymentService.createPaymentIntent({
702
+ amount: 1000,
703
+ currency: 'usd',
704
+ });
705
+
706
+ expect(paymentIntent).toBeDefined();
707
+ expect(paymentIntent.amount).toBe(1000);
708
+ expect(paymentIntent.currency).toBe('usd');
709
+ expect(paymentIntent.status).toBe('requires_payment_method');
710
+ });
711
+
712
+ it('should confirm payment intent with test card', async () => {
713
+ // Create payment intent
714
+ const paymentIntent = await paymentService.createPaymentIntent({
715
+ amount: 1000,
716
+ currency: 'usd',
717
+ });
718
+
719
+ // Create test payment method
720
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
721
+ const paymentMethod = await stripe.paymentMethods.create({
722
+ type: 'card',
723
+ card: {
724
+ number: '4242424242424242',
725
+ exp_month: 12,
726
+ exp_year: 2034,
727
+ cvc: '123',
728
+ },
729
+ });
730
+
731
+ // Confirm payment
732
+ const confirmed = await paymentService.confirmPaymentIntent(
733
+ paymentIntent.id,
734
+ paymentMethod.id
735
+ );
736
+
737
+ expect(confirmed.status).toBe('succeeded');
738
+ });
739
+ });
740
+
741
+ describe('Customer Management', () => {
742
+ it('should create a customer', async () => {
743
+ const customer = await paymentService.createCustomer({
744
+ email: 'test@example.com',
745
+ name: 'Test User',
746
+ });
747
+
748
+ expect(customer).toBeDefined();
749
+ expect(customer.email).toBe('test@example.com');
750
+ });
751
+
752
+ it('should attach payment method to customer', async () => {
753
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
754
+
755
+ // Create customer
756
+ const customer = await paymentService.createCustomer({
757
+ email: 'test@example.com',
758
+ });
759
+
760
+ // Create payment method
761
+ const paymentMethod = await stripe.paymentMethods.create({
762
+ type: 'card',
763
+ card: {
764
+ number: '4242424242424242',
765
+ exp_month: 12,
766
+ exp_year: 2034,
767
+ cvc: '123',
768
+ },
769
+ });
770
+
771
+ // Attach payment method
772
+ const attached = await paymentService.attachPaymentMethod(
773
+ paymentMethod.id,
774
+ customer.id
775
+ );
776
+
777
+ expect(attached.customer).toBe(customer.id);
778
+ });
779
+ });
780
+
781
+ describe('Refunds', () => {
782
+ it('should create a refund', async () => {
783
+ // First create and confirm a payment
784
+ const paymentIntent = await paymentService.createPaymentIntent({
785
+ amount: 1000,
786
+ currency: 'usd',
787
+ });
788
+
789
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
790
+ const paymentMethod = await stripe.paymentMethods.create({
791
+ type: 'card',
792
+ card: {
793
+ number: '4242424242424242',
794
+ exp_month: 12,
795
+ exp_year: 2034,
796
+ cvc: '123',
797
+ },
798
+ });
799
+
800
+ await paymentService.confirmPaymentIntent(paymentIntent.id, paymentMethod.id);
801
+
802
+ // Create refund
803
+ const refund = await paymentService.createRefund({
804
+ paymentIntentId: paymentIntent.id,
805
+ reason: 'requested_by_customer',
806
+ });
807
+
808
+ expect(refund).toBeDefined();
809
+ expect(refund.status).toBe('succeeded');
810
+ });
811
+ });
812
+ });
813
+ ```
814
+
815
+ ### 5. Security Checklist
816
+
817
+ **Backend Security**:
818
+ - [ ] NEVER log full card numbers or CVV
819
+ - [ ] Use HTTPS only (enforce TLS 1.2+)
820
+ - [ ] Validate webhook signatures
821
+ - [ ] Implement rate limiting on payment endpoints
822
+ - [ ] Store Stripe IDs, not card details
823
+ - [ ] Use environment variables for keys
824
+ - [ ] Implement idempotency keys for retries
825
+ - [ ] Sanitize user inputs
826
+ - [ ] Enable CSRF protection
827
+ - [ ] Use secure session management
828
+
829
+ **Frontend Security**:
830
+ - [ ] Use Stripe.js (never raw card inputs)
831
+ - [ ] Load Stripe.js from CDN (integrity check)
832
+ - [ ] Never send card data to your server
833
+ - [ ] Implement CSP headers
834
+ - [ ] Use HTTPS only
835
+ - [ ] Clear sensitive data from memory
836
+ - [ ] Disable autocomplete on card fields
837
+ - [ ] Implement proper error handling
838
+
839
+ **Monitoring**:
840
+ - [ ] Log all payment attempts
841
+ - [ ] Monitor failed payment rates
842
+ - [ ] Set up alerts for unusual activity
843
+ - [ ] Track refund rates
844
+ - [ ] Monitor webhook delivery
845
+ - [ ] Implement fraud detection
846
+
847
+ ### 6. Production Deployment
848
+
849
+ **Pre-launch Checklist**:
850
+
851
+ 1. **Update API Keys**:
852
+ - Switch from test keys (`sk_test_`, `pk_test_`) to live keys
853
+ - Update webhook endpoint with live webhook secret
854
+ - Test with live mode in Stripe Dashboard
855
+
856
+ 2. **Webhook Configuration**:
857
+ ```bash
858
+ # Register webhook in Stripe Dashboard
859
+ # URL: https://yourdomain.com/api/webhooks/stripe
860
+ # Events: payment_intent.succeeded, payment_intent.payment_failed,
861
+ # customer.subscription.*, charge.refunded
862
+ ```
863
+
864
+ 3. **Enable Radar** (fraud detection):
865
+ - Configure Radar rules in Stripe Dashboard
866
+ - Enable 3D Secure for high-risk payments
867
+ - Set up risk score thresholds
868
+
869
+ 4. **Tax Configuration**:
870
+ - Enable Stripe Tax if needed
871
+ - Configure tax rates by location
872
+ - Set up tax reporting
873
+
874
+ 5. **Business Verification**:
875
+ - Complete business verification in Stripe
876
+ - Add business information
877
+ - Verify bank account for payouts
878
+
879
+ 6. **Monitoring**:
880
+ - Set up Sentry or similar for error tracking
881
+ - Configure log aggregation (Datadog, Splunk)
882
+ - Set up uptime monitoring for webhook endpoint
883
+ - Create alerts for failed payments
884
+
885
+ ## Output Deliverables
886
+
887
+ When you complete this setup, provide:
888
+
889
+ 1. **Configured Files**:
890
+ - `.env` template with all required variables
891
+ - Backend service with payment methods
892
+ - API routes with error handling
893
+ - Frontend components (PaymentForm, CheckoutButton)
894
+
895
+ 2. **Documentation**:
896
+ - API endpoint documentation
897
+ - Testing guide with test cards
898
+ - Deployment checklist
899
+ - Security audit report
900
+
901
+ 3. **Testing**:
902
+ - Integration tests for payment flows
903
+ - Test scenarios for edge cases
904
+ - Webhook handling tests
905
+
906
+ 4. **Deployment**:
907
+ - Environment-specific configurations
908
+ - Database migration scripts (if storing payment records)
909
+ - Monitoring setup guide
910
+
911
+ ## Resources
912
+
913
+ - **Stripe Documentation**: https://stripe.com/docs
914
+ - **Stripe.js Reference**: https://stripe.com/docs/js
915
+ - **Webhook Testing**: Use Stripe CLI (`stripe listen --forward-to localhost:3000/api/webhooks/stripe`)
916
+ - **Test Cards**: https://stripe.com/docs/testing
917
+
918
+ ## Best Practices
919
+
920
+ 1. **Always use Stripe.js** for card collection (PCI compliance)
921
+ 2. **Verify webhooks** with signature validation
922
+ 3. **Handle errors gracefully** with user-friendly messages
923
+ 4. **Test thoroughly** with all test cards before production
924
+ 5. **Monitor payment success rates** and investigate drops
925
+ 6. **Implement retry logic** for API failures
926
+ 7. **Use metadata** to link payments to your database records
927
+ 8. **Never expose secret keys** in frontend code
928
+ 9. **Implement idempotency** for payment operations
929
+ 10. **Keep Stripe.js updated** to latest version
930
+
931
+ Start with test mode, verify all flows work correctly, then switch to live mode with the same code.