omgkit 2.0.7 → 2.1.1

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 (27) hide show
  1. package/package.json +2 -2
  2. package/plugin/skills/backend/api-architecture/SKILL.md +857 -0
  3. package/plugin/skills/backend/caching-strategies/SKILL.md +755 -0
  4. package/plugin/skills/backend/event-driven-architecture/SKILL.md +753 -0
  5. package/plugin/skills/backend/real-time-systems/SKILL.md +635 -0
  6. package/plugin/skills/databases/database-optimization/SKILL.md +571 -0
  7. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/monorepo-management/SKILL.md +595 -0
  10. package/plugin/skills/devops/observability/SKILL.md +622 -0
  11. package/plugin/skills/devops/performance-profiling/SKILL.md +905 -0
  12. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  13. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  14. package/plugin/skills/frontend/advanced-ui-design/SKILL.md +426 -0
  15. package/plugin/skills/integrations/ai-integration/SKILL.md +730 -0
  16. package/plugin/skills/integrations/payment-integration/SKILL.md +735 -0
  17. package/plugin/skills/languages/python/SKILL.md +489 -25
  18. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  19. package/plugin/skills/methodology/problem-solving/SKILL.md +355 -0
  20. package/plugin/skills/methodology/research-validation/SKILL.md +668 -0
  21. package/plugin/skills/methodology/sequential-thinking/SKILL.md +260 -0
  22. package/plugin/skills/mobile/mobile-development/SKILL.md +756 -0
  23. package/plugin/skills/security/security-hardening/SKILL.md +633 -0
  24. package/plugin/skills/tools/document-processing/SKILL.md +916 -0
  25. package/plugin/skills/tools/image-processing/SKILL.md +748 -0
  26. package/plugin/skills/tools/mcp-development/SKILL.md +883 -0
  27. package/plugin/skills/tools/media-processing/SKILL.md +831 -0
@@ -0,0 +1,735 @@
1
+ ---
2
+ name: payment-integration
3
+ description: Enterprise payment processing with Stripe, PayPal, and LemonSqueezy including subscriptions, webhooks, and PCI compliance
4
+ category: integrations
5
+ triggers:
6
+ - payment integration
7
+ - stripe
8
+ - paypal
9
+ - lemonsqueezy
10
+ - checkout
11
+ - subscription billing
12
+ - payment processing
13
+ ---
14
+
15
+ # Payment Integration
16
+
17
+ Enterprise-grade **payment processing** with Stripe, PayPal, and LemonSqueezy. This skill covers checkout flows, subscription management, webhook handling, and PCI compliance patterns.
18
+
19
+ ## Purpose
20
+
21
+ Implement secure, reliable payment systems:
22
+
23
+ - Process one-time and recurring payments
24
+ - Handle subscription lifecycle management
25
+ - Implement secure webhook processing
26
+ - Manage refunds and disputes
27
+ - Ensure PCI DSS compliance
28
+ - Support multiple payment methods
29
+
30
+ ## Features
31
+
32
+ ### 1. Stripe Integration
33
+
34
+ ```typescript
35
+ import Stripe from 'stripe';
36
+
37
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
38
+ apiVersion: '2023-10-16',
39
+ typescript: true,
40
+ });
41
+
42
+ // Create checkout session
43
+ async function createCheckoutSession(
44
+ items: CartItem[],
45
+ customerId?: string,
46
+ metadata?: Record<string, string>
47
+ ): Promise<Stripe.Checkout.Session> {
48
+ const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = items.map(item => ({
49
+ price_data: {
50
+ currency: 'usd',
51
+ product_data: {
52
+ name: item.name,
53
+ description: item.description,
54
+ images: item.images,
55
+ metadata: { productId: item.id },
56
+ },
57
+ unit_amount: Math.round(item.price * 100), // Convert to cents
58
+ },
59
+ quantity: item.quantity,
60
+ }));
61
+
62
+ return stripe.checkout.sessions.create({
63
+ mode: 'payment',
64
+ line_items: lineItems,
65
+ customer: customerId,
66
+ success_url: `${process.env.APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
67
+ cancel_url: `${process.env.APP_URL}/checkout/cancel`,
68
+ metadata,
69
+ payment_intent_data: {
70
+ metadata,
71
+ },
72
+ shipping_address_collection: {
73
+ allowed_countries: ['US', 'CA', 'GB'],
74
+ },
75
+ automatic_tax: { enabled: true },
76
+ });
77
+ }
78
+
79
+ // Create subscription
80
+ async function createSubscription(
81
+ customerId: string,
82
+ priceId: string,
83
+ options?: {
84
+ trialDays?: number;
85
+ couponId?: string;
86
+ metadata?: Record<string, string>;
87
+ }
88
+ ): Promise<Stripe.Subscription> {
89
+ const params: Stripe.SubscriptionCreateParams = {
90
+ customer: customerId,
91
+ items: [{ price: priceId }],
92
+ payment_behavior: 'default_incomplete',
93
+ payment_settings: {
94
+ save_default_payment_method: 'on_subscription',
95
+ },
96
+ expand: ['latest_invoice.payment_intent'],
97
+ metadata: options?.metadata,
98
+ };
99
+
100
+ if (options?.trialDays) {
101
+ params.trial_period_days = options.trialDays;
102
+ }
103
+
104
+ if (options?.couponId) {
105
+ params.coupon = options.couponId;
106
+ }
107
+
108
+ return stripe.subscriptions.create(params);
109
+ }
110
+
111
+ // Handle subscription changes
112
+ async function updateSubscription(
113
+ subscriptionId: string,
114
+ newPriceId: string,
115
+ prorationBehavior: 'create_prorations' | 'none' | 'always_invoice' = 'create_prorations'
116
+ ): Promise<Stripe.Subscription> {
117
+ const subscription = await stripe.subscriptions.retrieve(subscriptionId);
118
+
119
+ return stripe.subscriptions.update(subscriptionId, {
120
+ items: [{
121
+ id: subscription.items.data[0].id,
122
+ price: newPriceId,
123
+ }],
124
+ proration_behavior: prorationBehavior,
125
+ });
126
+ }
127
+
128
+ // Cancel subscription
129
+ async function cancelSubscription(
130
+ subscriptionId: string,
131
+ cancelImmediately: boolean = false
132
+ ): Promise<Stripe.Subscription> {
133
+ if (cancelImmediately) {
134
+ return stripe.subscriptions.cancel(subscriptionId);
135
+ }
136
+
137
+ return stripe.subscriptions.update(subscriptionId, {
138
+ cancel_at_period_end: true,
139
+ });
140
+ }
141
+
142
+ // Process refund
143
+ async function processRefund(
144
+ paymentIntentId: string,
145
+ amount?: number, // Partial refund in cents
146
+ reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'
147
+ ): Promise<Stripe.Refund> {
148
+ return stripe.refunds.create({
149
+ payment_intent: paymentIntentId,
150
+ amount, // Omit for full refund
151
+ reason,
152
+ });
153
+ }
154
+ ```
155
+
156
+ ### 2. Webhook Handling
157
+
158
+ ```typescript
159
+ import { buffer } from 'micro';
160
+ import type { NextApiRequest, NextApiResponse } from 'next';
161
+
162
+ // Webhook handler with signature verification
163
+ async function handleStripeWebhook(
164
+ req: NextApiRequest,
165
+ res: NextApiResponse
166
+ ): Promise<void> {
167
+ if (req.method !== 'POST') {
168
+ res.setHeader('Allow', 'POST');
169
+ res.status(405).end('Method Not Allowed');
170
+ return;
171
+ }
172
+
173
+ const buf = await buffer(req);
174
+ const sig = req.headers['stripe-signature'] as string;
175
+
176
+ let event: Stripe.Event;
177
+
178
+ try {
179
+ event = stripe.webhooks.constructEvent(
180
+ buf,
181
+ sig,
182
+ process.env.STRIPE_WEBHOOK_SECRET!
183
+ );
184
+ } catch (err) {
185
+ console.error('Webhook signature verification failed:', err);
186
+ res.status(400).send(`Webhook Error: ${err.message}`);
187
+ return;
188
+ }
189
+
190
+ // Handle events with idempotency
191
+ const idempotencyKey = event.id;
192
+ const processed = await isEventProcessed(idempotencyKey);
193
+
194
+ if (processed) {
195
+ res.status(200).json({ received: true, duplicate: true });
196
+ return;
197
+ }
198
+
199
+ try {
200
+ await processWebhookEvent(event);
201
+ await markEventProcessed(idempotencyKey);
202
+ res.status(200).json({ received: true });
203
+ } catch (err) {
204
+ console.error('Webhook processing error:', err);
205
+ // Return 200 to prevent retries for business logic errors
206
+ // Return 500 for transient errors that should be retried
207
+ res.status(err.retryable ? 500 : 200).json({ error: err.message });
208
+ }
209
+ }
210
+
211
+ // Event processor
212
+ async function processWebhookEvent(event: Stripe.Event): Promise<void> {
213
+ switch (event.type) {
214
+ case 'checkout.session.completed': {
215
+ const session = event.data.object as Stripe.Checkout.Session;
216
+ await handleCheckoutComplete(session);
217
+ break;
218
+ }
219
+
220
+ case 'customer.subscription.created':
221
+ case 'customer.subscription.updated': {
222
+ const subscription = event.data.object as Stripe.Subscription;
223
+ await syncSubscriptionStatus(subscription);
224
+ break;
225
+ }
226
+
227
+ case 'customer.subscription.deleted': {
228
+ const subscription = event.data.object as Stripe.Subscription;
229
+ await handleSubscriptionCanceled(subscription);
230
+ break;
231
+ }
232
+
233
+ case 'invoice.payment_succeeded': {
234
+ const invoice = event.data.object as Stripe.Invoice;
235
+ await handlePaymentSuccess(invoice);
236
+ break;
237
+ }
238
+
239
+ case 'invoice.payment_failed': {
240
+ const invoice = event.data.object as Stripe.Invoice;
241
+ await handlePaymentFailure(invoice);
242
+ break;
243
+ }
244
+
245
+ case 'charge.dispute.created': {
246
+ const dispute = event.data.object as Stripe.Dispute;
247
+ await handleDispute(dispute);
248
+ break;
249
+ }
250
+
251
+ default:
252
+ console.log(`Unhandled event type: ${event.type}`);
253
+ }
254
+ }
255
+
256
+ // Subscription sync
257
+ async function syncSubscriptionStatus(subscription: Stripe.Subscription): Promise<void> {
258
+ await db.subscription.upsert({
259
+ where: { stripeSubscriptionId: subscription.id },
260
+ update: {
261
+ status: subscription.status,
262
+ currentPeriodStart: new Date(subscription.current_period_start * 1000),
263
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
264
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
265
+ priceId: subscription.items.data[0].price.id,
266
+ },
267
+ create: {
268
+ stripeSubscriptionId: subscription.id,
269
+ stripeCustomerId: subscription.customer as string,
270
+ status: subscription.status,
271
+ currentPeriodStart: new Date(subscription.current_period_start * 1000),
272
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
273
+ priceId: subscription.items.data[0].price.id,
274
+ },
275
+ });
276
+ }
277
+ ```
278
+
279
+ ### 3. PayPal Integration
280
+
281
+ ```typescript
282
+ import { PayPalHttpClient, OrdersCreateRequest, OrdersCaptureRequest } from '@paypal/checkout-server-sdk';
283
+
284
+ // PayPal client setup
285
+ function getPayPalClient(): PayPalHttpClient {
286
+ const environment = process.env.NODE_ENV === 'production'
287
+ ? new paypal.core.LiveEnvironment(
288
+ process.env.PAYPAL_CLIENT_ID!,
289
+ process.env.PAYPAL_CLIENT_SECRET!
290
+ )
291
+ : new paypal.core.SandboxEnvironment(
292
+ process.env.PAYPAL_CLIENT_ID!,
293
+ process.env.PAYPAL_CLIENT_SECRET!
294
+ );
295
+
296
+ return new PayPalHttpClient(environment);
297
+ }
298
+
299
+ // Create PayPal order
300
+ async function createPayPalOrder(
301
+ items: CartItem[],
302
+ shippingCost: number = 0
303
+ ): Promise<PayPalOrder> {
304
+ const client = getPayPalClient();
305
+
306
+ const itemTotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
307
+ const total = itemTotal + shippingCost;
308
+
309
+ const request = new OrdersCreateRequest();
310
+ request.prefer('return=representation');
311
+ request.requestBody({
312
+ intent: 'CAPTURE',
313
+ purchase_units: [{
314
+ amount: {
315
+ currency_code: 'USD',
316
+ value: total.toFixed(2),
317
+ breakdown: {
318
+ item_total: { currency_code: 'USD', value: itemTotal.toFixed(2) },
319
+ shipping: { currency_code: 'USD', value: shippingCost.toFixed(2) },
320
+ },
321
+ },
322
+ items: items.map(item => ({
323
+ name: item.name,
324
+ unit_amount: { currency_code: 'USD', value: item.price.toFixed(2) },
325
+ quantity: item.quantity.toString(),
326
+ category: 'PHYSICAL_GOODS',
327
+ })),
328
+ }],
329
+ application_context: {
330
+ brand_name: process.env.APP_NAME,
331
+ landing_page: 'BILLING',
332
+ user_action: 'PAY_NOW',
333
+ return_url: `${process.env.APP_URL}/checkout/paypal/success`,
334
+ cancel_url: `${process.env.APP_URL}/checkout/paypal/cancel`,
335
+ },
336
+ });
337
+
338
+ const response = await client.execute(request);
339
+ return response.result;
340
+ }
341
+
342
+ // Capture PayPal payment
343
+ async function capturePayPalOrder(orderId: string): Promise<PayPalCapture> {
344
+ const client = getPayPalClient();
345
+ const request = new OrdersCaptureRequest(orderId);
346
+ request.prefer('return=representation');
347
+
348
+ const response = await client.execute(request);
349
+
350
+ if (response.result.status === 'COMPLETED') {
351
+ await handlePayPalPaymentComplete(response.result);
352
+ }
353
+
354
+ return response.result;
355
+ }
356
+ ```
357
+
358
+ ### 4. Subscription Management UI
359
+
360
+ ```typescript
361
+ // Subscription management component
362
+ interface SubscriptionManagerProps {
363
+ subscription: UserSubscription;
364
+ availablePlans: Plan[];
365
+ }
366
+
367
+ export function SubscriptionManager({ subscription, availablePlans }: SubscriptionManagerProps) {
368
+ const [isLoading, setIsLoading] = useState(false);
369
+
370
+ async function handleUpgrade(newPriceId: string) {
371
+ setIsLoading(true);
372
+ try {
373
+ await updateSubscription(subscription.id, newPriceId);
374
+ toast.success('Subscription updated successfully');
375
+ } catch (error) {
376
+ toast.error('Failed to update subscription');
377
+ } finally {
378
+ setIsLoading(false);
379
+ }
380
+ }
381
+
382
+ async function handleCancel() {
383
+ if (!confirm('Are you sure you want to cancel your subscription?')) return;
384
+
385
+ setIsLoading(true);
386
+ try {
387
+ await cancelSubscription(subscription.id);
388
+ toast.success('Subscription will be canceled at the end of the billing period');
389
+ } catch (error) {
390
+ toast.error('Failed to cancel subscription');
391
+ } finally {
392
+ setIsLoading(false);
393
+ }
394
+ }
395
+
396
+ async function handleReactivate() {
397
+ setIsLoading(true);
398
+ try {
399
+ await reactivateSubscription(subscription.id);
400
+ toast.success('Subscription reactivated');
401
+ } catch (error) {
402
+ toast.error('Failed to reactivate subscription');
403
+ } finally {
404
+ setIsLoading(false);
405
+ }
406
+ }
407
+
408
+ return (
409
+ <div className="subscription-manager">
410
+ <div className="current-plan">
411
+ <h3>Current Plan: {subscription.plan.name}</h3>
412
+ <p>Status: {subscription.status}</p>
413
+ <p>Renews: {format(subscription.currentPeriodEnd, 'MMM d, yyyy')}</p>
414
+ </div>
415
+
416
+ {subscription.cancelAtPeriodEnd && (
417
+ <Alert variant="warning">
418
+ Your subscription will end on {format(subscription.currentPeriodEnd, 'MMM d, yyyy')}
419
+ <Button onClick={handleReactivate} disabled={isLoading}>
420
+ Reactivate
421
+ </Button>
422
+ </Alert>
423
+ )}
424
+
425
+ <div className="available-plans">
426
+ <h4>Available Plans</h4>
427
+ {availablePlans.map(plan => (
428
+ <PlanCard
429
+ key={plan.id}
430
+ plan={plan}
431
+ isCurrent={plan.priceId === subscription.priceId}
432
+ onSelect={() => handleUpgrade(plan.priceId)}
433
+ disabled={isLoading}
434
+ />
435
+ ))}
436
+ </div>
437
+
438
+ {!subscription.cancelAtPeriodEnd && (
439
+ <Button variant="destructive" onClick={handleCancel} disabled={isLoading}>
440
+ Cancel Subscription
441
+ </Button>
442
+ )}
443
+ </div>
444
+ );
445
+ }
446
+ ```
447
+
448
+ ### 5. LemonSqueezy Integration
449
+
450
+ ```typescript
451
+ import { lemonSqueezySetup, createCheckout, getSubscription } from '@lemonsqueezy/lemonsqueezy.js';
452
+
453
+ // Initialize LemonSqueezy
454
+ lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY! });
455
+
456
+ // Create LemonSqueezy checkout
457
+ async function createLemonSqueezyCheckout(
458
+ variantId: string,
459
+ customerEmail: string,
460
+ metadata?: Record<string, string>
461
+ ): Promise<string> {
462
+ const checkout = await createCheckout(
463
+ process.env.LEMONSQUEEZY_STORE_ID!,
464
+ variantId,
465
+ {
466
+ checkoutData: {
467
+ email: customerEmail,
468
+ custom: metadata,
469
+ },
470
+ checkoutOptions: {
471
+ embed: false,
472
+ logo: true,
473
+ dark: false,
474
+ },
475
+ productOptions: {
476
+ enabledVariants: [parseInt(variantId)],
477
+ redirectUrl: `${process.env.APP_URL}/checkout/success`,
478
+ },
479
+ }
480
+ );
481
+
482
+ return checkout.data.attributes.url;
483
+ }
484
+
485
+ // LemonSqueezy webhook handler
486
+ async function handleLemonSqueezyWebhook(req: NextApiRequest): Promise<void> {
487
+ const signature = req.headers['x-signature'] as string;
488
+ const payload = JSON.stringify(req.body);
489
+
490
+ // Verify signature
491
+ const hmac = crypto.createHmac('sha256', process.env.LEMONSQUEEZY_WEBHOOK_SECRET!);
492
+ hmac.update(payload);
493
+ const expectedSignature = hmac.digest('hex');
494
+
495
+ if (signature !== expectedSignature) {
496
+ throw new Error('Invalid webhook signature');
497
+ }
498
+
499
+ const { event_name, data } = req.body;
500
+
501
+ switch (event_name) {
502
+ case 'subscription_created':
503
+ await handleLSSubscriptionCreated(data);
504
+ break;
505
+ case 'subscription_updated':
506
+ await handleLSSubscriptionUpdated(data);
507
+ break;
508
+ case 'subscription_cancelled':
509
+ await handleLSSubscriptionCancelled(data);
510
+ break;
511
+ case 'order_created':
512
+ await handleLSOrderCreated(data);
513
+ break;
514
+ }
515
+ }
516
+ ```
517
+
518
+ ### 6. Payment Security
519
+
520
+ ```typescript
521
+ // PCI-compliant payment handling patterns
522
+
523
+ // Never log sensitive payment data
524
+ const SENSITIVE_FIELDS = ['card_number', 'cvv', 'cvc', 'exp_month', 'exp_year'];
525
+
526
+ function sanitizeForLogging(data: Record<string, any>): Record<string, any> {
527
+ const sanitized = { ...data };
528
+
529
+ for (const field of SENSITIVE_FIELDS) {
530
+ if (field in sanitized) {
531
+ sanitized[field] = '[REDACTED]';
532
+ }
533
+ }
534
+
535
+ return sanitized;
536
+ }
537
+
538
+ // Secure API endpoint
539
+ async function createPaymentIntent(req: NextApiRequest, res: NextApiResponse) {
540
+ // Validate request
541
+ const { amount, currency, customerId } = req.body;
542
+
543
+ if (!amount || amount < 50) { // Minimum $0.50
544
+ return res.status(400).json({ error: 'Invalid amount' });
545
+ }
546
+
547
+ // Use idempotency key
548
+ const idempotencyKey = req.headers['idempotency-key'] as string;
549
+
550
+ if (!idempotencyKey) {
551
+ return res.status(400).json({ error: 'Idempotency key required' });
552
+ }
553
+
554
+ try {
555
+ const paymentIntent = await stripe.paymentIntents.create(
556
+ {
557
+ amount,
558
+ currency: currency || 'usd',
559
+ customer: customerId,
560
+ automatic_payment_methods: { enabled: true },
561
+ metadata: {
562
+ userId: req.user.id,
563
+ },
564
+ },
565
+ { idempotencyKey }
566
+ );
567
+
568
+ // Only return necessary data
569
+ res.json({
570
+ clientSecret: paymentIntent.client_secret,
571
+ paymentIntentId: paymentIntent.id,
572
+ });
573
+ } catch (error) {
574
+ console.error('Payment error:', sanitizeForLogging(error));
575
+
576
+ if (error.type === 'StripeCardError') {
577
+ return res.status(400).json({ error: error.message });
578
+ }
579
+
580
+ res.status(500).json({ error: 'Payment processing failed' });
581
+ }
582
+ }
583
+
584
+ // Webhook security middleware
585
+ function verifyWebhookSignature(
586
+ payload: Buffer,
587
+ signature: string,
588
+ secret: string
589
+ ): boolean {
590
+ try {
591
+ stripe.webhooks.constructEvent(payload, signature, secret);
592
+ return true;
593
+ } catch {
594
+ return false;
595
+ }
596
+ }
597
+ ```
598
+
599
+ ## Use Cases
600
+
601
+ ### 1. E-commerce Checkout
602
+
603
+ ```typescript
604
+ // Complete e-commerce checkout flow
605
+ async function processCheckout(cart: Cart, user: User): Promise<CheckoutResult> {
606
+ // Calculate totals
607
+ const subtotal = calculateSubtotal(cart.items);
608
+ const tax = await calculateTax(subtotal, user.address);
609
+ const shipping = await calculateShipping(cart.items, user.address);
610
+ const total = subtotal + tax + shipping;
611
+
612
+ // Create Stripe checkout
613
+ const session = await createCheckoutSession(cart.items, user.stripeCustomerId, {
614
+ orderId: generateOrderId(),
615
+ userId: user.id,
616
+ });
617
+
618
+ // Create pending order
619
+ await db.order.create({
620
+ data: {
621
+ userId: user.id,
622
+ status: 'pending',
623
+ subtotal,
624
+ tax,
625
+ shipping,
626
+ total,
627
+ stripeSessionId: session.id,
628
+ items: {
629
+ create: cart.items.map(item => ({
630
+ productId: item.productId,
631
+ quantity: item.quantity,
632
+ price: item.price,
633
+ })),
634
+ },
635
+ },
636
+ });
637
+
638
+ return {
639
+ checkoutUrl: session.url,
640
+ sessionId: session.id,
641
+ };
642
+ }
643
+ ```
644
+
645
+ ### 2. SaaS Subscription
646
+
647
+ ```typescript
648
+ // SaaS subscription management
649
+ async function handlePlanChange(
650
+ userId: string,
651
+ newPlanId: string
652
+ ): Promise<PlanChangeResult> {
653
+ const user = await db.user.findUnique({
654
+ where: { id: userId },
655
+ include: { subscription: true },
656
+ });
657
+
658
+ if (!user.subscription) {
659
+ // New subscription
660
+ const session = await createSubscriptionCheckout(user, newPlanId);
661
+ return { action: 'redirect', url: session.url };
662
+ }
663
+
664
+ const currentPlan = await getPlan(user.subscription.priceId);
665
+ const newPlan = await getPlan(newPlanId);
666
+
667
+ if (newPlan.price > currentPlan.price) {
668
+ // Upgrade - charge prorated amount
669
+ await updateSubscription(user.subscription.stripeSubscriptionId, newPlanId, 'create_prorations');
670
+ return { action: 'upgraded', newPlan };
671
+ } else {
672
+ // Downgrade - apply at period end
673
+ await updateSubscription(user.subscription.stripeSubscriptionId, newPlanId, 'none');
674
+ return { action: 'scheduled', effectiveDate: user.subscription.currentPeriodEnd };
675
+ }
676
+ }
677
+ ```
678
+
679
+ ## Best Practices
680
+
681
+ ### Do's
682
+
683
+ - **Use idempotency keys** - Prevent duplicate charges
684
+ - **Verify webhook signatures** - Always validate webhook authenticity
685
+ - **Handle all payment states** - Success, failure, pending, disputed
686
+ - **Store payment references** - Keep Stripe IDs for reconciliation
687
+ - **Test with sandbox** - Use test mode during development
688
+ - **Monitor for fraud** - Implement Stripe Radar or equivalent
689
+
690
+ ### Don'ts
691
+
692
+ - Never log full card numbers
693
+ - Never store CVV/CVC codes
694
+ - Never handle raw card data (use Stripe.js/Elements)
695
+ - Never skip signature verification
696
+ - Never trust client-side amounts
697
+ - Never expose secret keys in frontend
698
+
699
+ ### Security Checklist
700
+
701
+ ```markdown
702
+ ## Payment Security Checklist
703
+
704
+ ### PCI Compliance
705
+ - [ ] No raw card data on server
706
+ - [ ] Using Stripe.js/Elements for card collection
707
+ - [ ] Webhook signature verification enabled
708
+ - [ ] HTTPS only for all payment endpoints
709
+
710
+ ### Data Handling
711
+ - [ ] Sensitive fields excluded from logs
712
+ - [ ] Customer IDs used instead of card details
713
+ - [ ] Idempotency keys for all mutations
714
+ - [ ] Payment references stored securely
715
+
716
+ ### Fraud Prevention
717
+ - [ ] Stripe Radar enabled
718
+ - [ ] Address verification (AVS)
719
+ - [ ] 3D Secure enabled
720
+ - [ ] Velocity checks implemented
721
+ ```
722
+
723
+ ## Related Skills
724
+
725
+ - **backend-development** - Server-side payment handling
726
+ - **security** - PCI compliance and secure handling
727
+ - **oauth** - Customer authentication
728
+ - **api-architecture** - Payment API design
729
+
730
+ ## Reference Resources
731
+
732
+ - [Stripe Documentation](https://stripe.com/docs)
733
+ - [PayPal Developer](https://developer.paypal.com/)
734
+ - [LemonSqueezy Docs](https://docs.lemonsqueezy.com/)
735
+ - [PCI DSS Standards](https://www.pcisecuritystandards.org/)