| name | payment-integration |
| description | Enterprise payment processing with Stripe, PayPal, and LemonSqueezy including subscriptions, webhooks, and PCI compliance |
| category | integrations |
| triggers | ["payment integration","stripe","paypal","lemonsqueezy","checkout","subscription billing","payment processing"] |
Payment Integration
Enterprise-grade payment processing with Stripe, PayPal, and LemonSqueezy. This skill covers checkout flows, subscription management, webhook handling, and PCI compliance patterns.
Purpose
Implement secure, reliable payment systems:
- Process one-time and recurring payments
- Handle subscription lifecycle management
- Implement secure webhook processing
- Manage refunds and disputes
- Ensure PCI DSS compliance
- Support multiple payment methods
Features
1. Stripe Integration
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true,
});
async function createCheckoutSession(
items: CartItem[],
customerId?: string,
metadata?: Record<string, string>
): Promise<Stripe.Checkout.Session> {
const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = items.map(item => ({
price_data: {
currency: 'usd',
product_data: {
name: item.name,
description: item.description,
images: item.images,
metadata: { productId: item.id },
},
unit_amount: Math.round(item.price * 100),
},
quantity: item.quantity,
}));
return stripe.checkout.sessions.create({
mode: 'payment',
line_items: lineItems,
customer: customerId,
success_url: `${process.env.APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/checkout/cancel`,
metadata,
payment_intent_data: {
metadata,
},
shipping_address_collection: {
allowed_countries: ['US', 'CA', 'GB'],
},
automatic_tax: { enabled: true },
});
}
async function createSubscription(
customerId: string,
priceId: string,
options?: {
trialDays?: number;
couponId?: string;
metadata?: Record<string, string>;
}
): Promise<Stripe.Subscription> {
const params: Stripe.SubscriptionCreateParams = {
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
payment_settings: {
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
metadata: options?.metadata,
};
if (options?.trialDays) {
params.trial_period_days = options.trialDays;
}
if (options?.couponId) {
params.coupon = options.couponId;
}
return stripe.subscriptions.create(params);
}
async function updateSubscription(
subscriptionId: string,
newPriceId: string,
prorationBehavior: 'create_prorations' | 'none' | 'always_invoice' = 'create_prorations'
): Promise<Stripe.Subscription> {
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
return stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscription.items.data[0].id,
price: newPriceId,
}],
proration_behavior: prorationBehavior,
});
}
async function cancelSubscription(
subscriptionId: string,
cancelImmediately: boolean = false
): Promise<Stripe.Subscription> {
if (cancelImmediately) {
return stripe.subscriptions.cancel(subscriptionId);
}
return stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true,
});
}
async function processRefund(
paymentIntentId: string,
amount?: number,
reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'
): Promise<Stripe.Refund> {
return stripe.refunds.create({
payment_intent: paymentIntentId,
amount,
reason,
});
}
2. Webhook Handling
import { buffer } from 'micro';
import type { NextApiRequest, NextApiResponse } from 'next';
async function handleStripeWebhook(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
if (req.method !== 'POST') {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
return;
}
const buf = await buffer(req);
const sig = req.headers['stripe-signature'] as string;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
buf,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error(, err);
res.().();
;
}
idempotencyKey = event.;
processed = (idempotencyKey);
(processed) {
res.().({ : , : });
;
}
{
(event);
(idempotencyKey);
res.().({ : });
} (err) {
.(, err);
res.(err. ? : ).({ : err. });
}
}
(): <> {
(event.) {
: {
session = event.. ..;
(session);
;
}
:
: {
subscription = event.. .;
(subscription);
;
}
: {
subscription = event.. .;
(subscription);
;
}
: {
invoice = event.. .;
(invoice);
;
}
: {
invoice = event.. .;
(invoice);
;
}
: {
dispute = event.. .;
(dispute);
;
}
:
.();
}
}
(): <> {
db..({
: { : subscription. },
: {
: subscription.,
: (subscription. * ),
: (subscription. * ),
: subscription.,
: subscription..[]..,
},
: {
: subscription.,
: subscription. ,
: subscription.,
: (subscription. * ),
: (subscription. * ),
: subscription..[]..,
},
});
}
3. PayPal Integration
import { PayPalHttpClient, OrdersCreateRequest, OrdersCaptureRequest } from '@paypal/checkout-server-sdk';
function getPayPalClient(): PayPalHttpClient {
const environment = process.env.NODE_ENV === 'production'
? new paypal.core.LiveEnvironment(
process.env.PAYPAL_CLIENT_ID!,
process.env.PAYPAL_CLIENT_SECRET!
)
: new paypal.core.SandboxEnvironment(
process.env.PAYPAL_CLIENT_ID!,
process.env.PAYPAL_CLIENT_SECRET!
);
return new PayPalHttpClient(environment);
}
async function createPayPalOrder(
items: CartItem[],
shippingCost: number = 0
): Promise<PayPalOrder> {
const client = getPayPalClient();
const itemTotal = items.reduce(() => sum + item. * item., );
total = itemTotal + shippingCost;
request = ();
request.();
request.({
: ,
: [{
: {
: ,
: total.(),
: {
: { : , : itemTotal.() },
: { : , : shippingCost.() },
},
},
: items.( ({
: item.,
: { : , : item..() },
: item..(),
: ,
})),
}],
: {
: process..,
: ,
: ,
: ,
: ,
},
});
response = client.(request);
response.;
}
(): <> {
client = ();
request = (orderId);
request.();
response = client.(request);
(response.. === ) {
(response.);
}
response.;
}
4. Subscription Management UI
interface SubscriptionManagerProps {
subscription: UserSubscription;
availablePlans: Plan[];
}
export function SubscriptionManager({ subscription, availablePlans }: SubscriptionManagerProps) {
const [isLoading, setIsLoading] = useState(false);
async function handleUpgrade(newPriceId: string) {
setIsLoading(true);
try {
await updateSubscription(subscription.id, newPriceId);
toast.success('Subscription updated successfully');
} catch (error) {
toast.error('Failed to update subscription');
} finally {
setIsLoading(false);
}
}
async function handleCancel() {
if (!confirm('Are you sure you want to cancel your subscription?')) return;
setIsLoading(true);
try {
await cancelSubscription(subscription.);
toast.();
} (error) {
toast.();
} {
();
}
}
() {
();
{
(subscription.);
toast.();
} (error) {
toast.();
} {
();
}
}
(
);
}
5. LemonSqueezy Integration
import { lemonSqueezySetup, createCheckout, getSubscription } from '@lemonsqueezy/lemonsqueezy.js';
lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY! });
async function createLemonSqueezyCheckout(
variantId: string,
customerEmail: string,
metadata?: Record<string, string>
): Promise<string> {
const checkout = await createCheckout(
process.env.LEMONSQUEEZY_STORE_ID!,
variantId,
{
checkoutData: {
email: customerEmail,
custom: metadata,
},
checkoutOptions: {
embed: false,
logo: true,
dark: false,
},
productOptions: {
enabledVariants: [parseInt(variantId)],
redirectUrl: `${process.env.APP_URL}/checkout/success`,
},
}
);
return checkout.data.attributes.url;
}
(): <> {
signature = req.[] ;
payload = .(req.);
hmac = crypto.(, process..!);
hmac.(payload);
expectedSignature = hmac.();
(signature !== expectedSignature) {
();
}
{ event_name, data } = req.;
(event_name) {
:
(data);
;
:
(data);
;
:
(data);
;
:
(data);
;
}
}
6. Payment Security
const SENSITIVE_FIELDS = ['card_number', 'cvv', 'cvc', 'exp_month', 'exp_year'];
function sanitizeForLogging(data: Record<string, any>): Record<string, any> {
const sanitized = { ...data };
for (const field of SENSITIVE_FIELDS) {
if (field in sanitized) {
sanitized[field] = '[REDACTED]';
}
}
return sanitized;
}
async function createPaymentIntent(req: NextApiRequest, res: NextApiResponse) {
const { amount, currency, customerId } = req.body;
if (!amount || amount < 50) {
return res.status(400).json({ error: 'Invalid amount' });
}
idempotencyKey = req.[] ;
(!idempotencyKey) {
res.().({ : });
}
{
paymentIntent = stripe..(
{
amount,
: currency || ,
: customerId,
: { : },
: {
: req..,
},
},
{ idempotencyKey }
);
res.({
: paymentIntent.,
: paymentIntent.,
});
} (error) {
.(, (error));
(error. === ) {
res.().({ : error. });
}
res.().({ : });
}
}
(): {
{
stripe..(payload, signature, secret);
;
} {
;
}
}
Use Cases
1. E-commerce Checkout
async function processCheckout(cart: Cart, user: User): Promise<CheckoutResult> {
const subtotal = calculateSubtotal(cart.items);
const tax = await calculateTax(subtotal, user.address);
const shipping = await calculateShipping(cart.items, user.address);
const total = subtotal + tax + shipping;
const session = await createCheckoutSession(cart.items, user.stripeCustomerId, {
orderId: generateOrderId(),
userId: user.id,
});
await db.order.create({
data: {
userId: user.id,
status: 'pending',
subtotal,
tax,
shipping,
total,
stripeSessionId: session.id,
items: {
create: cart.items.( ({
: item.,
: item.,
: item.,
})),
},
},
});
{
: session.,
: session.,
};
}
2. SaaS Subscription
async function handlePlanChange(
userId: string,
newPlanId: string
): Promise<PlanChangeResult> {
const user = await db.user.findUnique({
where: { id: userId },
include: { subscription: true },
});
if (!user.subscription) {
const session = await createSubscriptionCheckout(user, newPlanId);
return { action: 'redirect', url: session.url };
}
const currentPlan = await getPlan(user.subscription.priceId);
const newPlan = await getPlan(newPlanId);
if (newPlan.price > currentPlan.price) {
await updateSubscription(user.subscription.stripeSubscriptionId, newPlanId, 'create_prorations');
return { action: , newPlan };
} {
(user.., newPlanId, );
{ : , : user.. };
}
}
Best Practices
Do's
- Use idempotency keys - Prevent duplicate charges
- Verify webhook signatures - Always validate webhook authenticity
- Handle all payment states - Success, failure, pending, disputed
- Store payment references - Keep Stripe IDs for reconciliation
- Test with sandbox - Use test mode during development
- Monitor for fraud - Implement Stripe Radar or equivalent
Don'ts
- Never log full card numbers
- Never store CVV/CVC codes
- Never handle raw card data (use Stripe.js/Elements)
- Never skip signature verification
- Never trust client-side amounts
- Never expose secret keys in frontend
Security Checklist
## Payment Security Checklist
### PCI Compliance
- [ ] No raw card data on server
- [ ] Using Stripe.js/Elements for card collection
- [ ] Webhook signature verification enabled
- [ ] HTTPS only for all payment endpoints
### Data Handling
- [ ] Sensitive fields excluded from logs
- [ ] Customer IDs used instead of card details
- [ ] Idempotency keys for all mutations
- [ ] Payment references stored securely
### Fraud Prevention
- [ ] Stripe Radar enabled
- [ ] Address verification (AVS)
- [ ] 3D Secure enabled
- [ ] Velocity checks implemented
Related Skills
- backend-development - Server-side payment handling
- security - PCI compliance and secure handling
- oauth - Customer authentication
- api-architecture - Payment API design
Reference Resources