
Payment Processing
- 1 installs
- 1 repo stars
- Updated January 22, 2026
- erikpr1994/jarvis-code
Implement Stripe or Polar payment flows: server-side checkout sessions, signature-verified webhooks, subscription lifecycle, and error handling.
About
Provides patterns for Stripe and Polar payments including server-created checkout sessions, verified webhook endpoints with idempotency, subscription handling, and user-friendly error mapping. A developer uses it when building checkout, webhooks, subscriptions, or refunds.
- Webhook signature verification and idempotency checks emphasized
- Covers both Stripe and Polar plus a security red-flags checklist
Payment Processing by the numbers
- 1 all-time installs (skills.sh)
- Ranked #3,818 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erikpr1994/jarvis-code --skill payment-processingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | January 22, 2026 |
| Repository | erikpr1994/jarvis-code ↗ |
What it does
Implement Stripe or Polar payment flows: server-side checkout sessions, signature-verified webhooks, subscription lifecycle, and error handling.
Files
Payment Processing
Overview
Payment integration patterns for Stripe and Polar. Covers checkout flows, webhook handling, subscription management, and error handling for secure, reliable payment processing.
When to Use
- Implementing checkout flow
- Setting up Stripe/Polar integration
- Handling payment webhooks
- Managing subscriptions
- Processing refunds
Quick Reference
| Component | Key Considerations |
|---|---|
| Checkout | Server-side session creation, redirect handling |
| Webhooks | Signature verification, idempotency, retry handling |
| Subscriptions | Lifecycle events, cancellation, upgrades |
| Errors | Graceful degradation, user messaging, logging |
---
Stripe Integration
Checkout Session (Server)
// app/api/checkout/route.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
export async function POST(req: Request) {
const { priceId, userId } = await req.json();
try {
const session = await stripe.checkout.sessions.create({
mode: 'subscription', // or 'payment' for one-time
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
customer_email: user.email, // Pre-fill email
metadata: {
userId, // Track in your system
},
});
return Response.json({ url: session.url });
} catch (error) {
console.error('Checkout error:', error);
return Response.json(
{ error: 'Failed to create checkout session' },
{ status: 500 }
);
}
}Client Redirect
// components/CheckoutButton.tsx
async function handleCheckout(priceId: string) {
setLoading(true);
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const { url, error } = await res.json();
if (error) throw new Error(error);
window.location.href = url; // Redirect to Stripe
} catch (error) {
toast.error('Failed to start checkout');
} finally {
setLoading(false);
}
}---
Webhook Handling
Webhook Endpoint
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get('stripe-signature')!;
let event: Stripe.Event;
// 1. Verify signature (CRITICAL)
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error('Webhook signature verification failed');
return Response.json({ error: 'Invalid signature' }, { status: 400 });
}
// 2. Handle event (with idempotency)
try {
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutComplete(event.data.object);
break;
case 'customer.subscription.created':
case 'customer.subscription.updated':
await handleSubscriptionChange(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event.data.object);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return Response.json({ received: true });
} catch (error) {
console.error('Webhook handler error:', error);
return Response.json({ error: 'Handler failed' }, { status: 500 });
}
}Webhook Handlers
async function handleCheckoutComplete(session: Stripe.Checkout.Session) {
const userId = session.metadata?.userId;
const subscriptionId = session.subscription as string;
// Idempotency check
const existing = await db.subscription.findUnique({
where: { stripeSubscriptionId: subscriptionId }
});
if (existing) return; // Already processed
await db.subscription.create({
data: {
userId,
stripeSubscriptionId: subscriptionId,
stripeCustomerId: session.customer as string,
status: 'active',
},
});
}
async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
await db.subscription.update({
where: { stripeSubscriptionId: subscription.id },
data: { status: 'canceled', canceledAt: new Date() },
});
}---
Polar Integration
Setup
// lib/polar.ts
import { Polar } from '@polar-sh/sdk';
export const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN!,
});Checkout with Polar
// app/api/polar/checkout/route.ts
export async function POST(req: Request) {
const { productId, userId } = await req.json();
const checkout = await polar.checkouts.create({
productId,
successUrl: `${process.env.NEXT_PUBLIC_URL}/success`,
metadata: { userId },
});
return Response.json({ url: checkout.url });
}Polar Webhooks
// app/api/webhooks/polar/route.ts
import { validateWebhookSignature } from '@polar-sh/sdk/webhooks';
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get('webhook-signature')!;
// Verify signature
const isValid = validateWebhookSignature(
body,
signature,
process.env.POLAR_WEBHOOK_SECRET!
);
if (!isValid) {
return Response.json({ error: 'Invalid signature' }, { status: 400 });
}
const event = JSON.parse(body);
switch (event.type) {
case 'subscription.created':
await handlePolarSubscription(event.data);
break;
case 'subscription.canceled':
await handlePolarCancellation(event.data);
break;
}
return Response.json({ received: true });
}---
Error Handling
Payment Errors
const STRIPE_ERROR_MESSAGES: Record<string, string> = {
card_declined: 'Your card was declined. Please try another card.',
insufficient_funds: 'Insufficient funds. Please try another card.',
expired_card: 'Your card has expired. Please update your payment method.',
incorrect_cvc: 'Incorrect CVC. Please check and try again.',
processing_error: 'Processing error. Please try again.',
default: 'Payment failed. Please try again or contact support.',
};
function getPaymentErrorMessage(error: Stripe.StripeError): string {
return STRIPE_ERROR_MESSAGES[error.code ?? 'default']
?? STRIPE_ERROR_MESSAGES.default;
}Graceful Degradation
async function createCheckout(priceId: string) {
try {
const session = await stripe.checkout.sessions.create({...});
return { success: true, url: session.url };
} catch (error) {
// Log for debugging
console.error('Stripe checkout failed:', error);
// Return user-friendly error
return {
success: false,
error: 'Unable to process payment. Please try again later.',
};
}
}---
Security Checklist
- [ ] Webhook signatures verified
- [ ] Secret keys in environment variables only
- [ ] HTTPS for all payment endpoints
- [ ] Idempotency keys for critical operations
- [ ] No sensitive data in client-side code
- [ ] PCI compliance requirements met
- [ ] Rate limiting on payment endpoints
---
Common Patterns
Subscription Status Check
async function hasActiveSubscription(userId: string): Promise<boolean> {
const sub = await db.subscription.findFirst({
where: {
userId,
status: { in: ['active', 'trialing'] },
},
});
return !!sub;
}Customer Portal
// Allow users to manage subscription
const portalSession = await stripe.billingPortal.sessions.create({
customer: stripeCustomerId,
return_url: `${process.env.NEXT_PUBLIC_URL}/account`,
});
return Response.json({ url: portalSession.url });---
Red Flags - STOP
Never:
- Log full card numbers or CVCs
- Store payment secrets in code
- Skip webhook signature verification
- Trust client-side payment data
- Process payments without idempotency
Always:
- Verify webhook signatures
- Use server-side session creation
- Handle all webhook event types
- Log payment events for debugging
- Test with Stripe/Polar test mode
---
Integration
Related skills: api-design, nextjs-patterns Testing: Use Stripe CLI for local webhook testing