home / skills / doanchienthangdev / omgkit / payment-integration
This skill integrates Stripe, PayPal, and LemonSqueezy for secure checkout, subscriptions, webhooks, and PCI-compliant payment flows.
npx playbooks add skill doanchienthangdev/omgkit --skill payment-integrationReview the files below or copy the command above to add this skill to your agents.
---
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
```typescript
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true,
});
// Create checkout session
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), // Convert to cents
},
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 },
});
}
// Create subscription
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);
}
// Handle subscription changes
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,
});
}
// Cancel subscription
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,
});
}
// Process refund
async function processRefund(
paymentIntentId: string,
amount?: number, // Partial refund in cents
reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'
): Promise<Stripe.Refund> {
return stripe.refunds.create({
payment_intent: paymentIntentId,
amount, // Omit for full refund
reason,
});
}
```
### 2. Webhook Handling
```typescript
import { buffer } from 'micro';
import type { NextApiRequest, NextApiResponse } from 'next';
// Webhook handler with signature verification
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('Webhook signature verification failed:', err);
res.status(400).send(`Webhook Error: ${err.message}`);
return;
}
// Handle events with idempotency
const idempotencyKey = event.id;
const processed = await isEventProcessed(idempotencyKey);
if (processed) {
res.status(200).json({ received: true, duplicate: true });
return;
}
try {
await processWebhookEvent(event);
await markEventProcessed(idempotencyKey);
res.status(200).json({ received: true });
} catch (err) {
console.error('Webhook processing error:', err);
// Return 200 to prevent retries for business logic errors
// Return 500 for transient errors that should be retried
res.status(err.retryable ? 500 : 200).json({ error: err.message });
}
}
// Event processor
async function processWebhookEvent(event: Stripe.Event): Promise<void> {
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await handleCheckoutComplete(session);
break;
}
case 'customer.subscription.created':
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription;
await syncSubscriptionStatus(subscription);
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionCanceled(subscription);
break;
}
case 'invoice.payment_succeeded': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentSuccess(invoice);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentFailure(invoice);
break;
}
case 'charge.dispute.created': {
const dispute = event.data.object as Stripe.Dispute;
await handleDispute(dispute);
break;
}
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
// Subscription sync
async function syncSubscriptionStatus(subscription: Stripe.Subscription): Promise<void> {
await db.subscription.upsert({
where: { stripeSubscriptionId: subscription.id },
update: {
status: subscription.status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
cancelAtPeriodEnd: subscription.cancel_at_period_end,
priceId: subscription.items.data[0].price.id,
},
create: {
stripeSubscriptionId: subscription.id,
stripeCustomerId: subscription.customer as string,
status: subscription.status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000),
priceId: subscription.items.data[0].price.id,
},
});
}
```
### 3. PayPal Integration
```typescript
import { PayPalHttpClient, OrdersCreateRequest, OrdersCaptureRequest } from '@paypal/checkout-server-sdk';
// PayPal client setup
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);
}
// Create PayPal order
async function createPayPalOrder(
items: CartItem[],
shippingCost: number = 0
): Promise<PayPalOrder> {
const client = getPayPalClient();
const itemTotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const total = itemTotal + shippingCost;
const request = new OrdersCreateRequest();
request.prefer('return=representation');
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
amount: {
currency_code: 'USD',
value: total.toFixed(2),
breakdown: {
item_total: { currency_code: 'USD', value: itemTotal.toFixed(2) },
shipping: { currency_code: 'USD', value: shippingCost.toFixed(2) },
},
},
items: items.map(item => ({
name: item.name,
unit_amount: { currency_code: 'USD', value: item.price.toFixed(2) },
quantity: item.quantity.toString(),
category: 'PHYSICAL_GOODS',
})),
}],
application_context: {
brand_name: process.env.APP_NAME,
landing_page: 'BILLING',
user_action: 'PAY_NOW',
return_url: `${process.env.APP_URL}/checkout/paypal/success`,
cancel_url: `${process.env.APP_URL}/checkout/paypal/cancel`,
},
});
const response = await client.execute(request);
return response.result;
}
// Capture PayPal payment
async function capturePayPalOrder(orderId: string): Promise<PayPalCapture> {
const client = getPayPalClient();
const request = new OrdersCaptureRequest(orderId);
request.prefer('return=representation');
const response = await client.execute(request);
if (response.result.status === 'COMPLETED') {
await handlePayPalPaymentComplete(response.result);
}
return response.result;
}
```
### 4. Subscription Management UI
```typescript
// Subscription management component
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.id);
toast.success('Subscription will be canceled at the end of the billing period');
} catch (error) {
toast.error('Failed to cancel subscription');
} finally {
setIsLoading(false);
}
}
async function handleReactivate() {
setIsLoading(true);
try {
await reactivateSubscription(subscription.id);
toast.success('Subscription reactivated');
} catch (error) {
toast.error('Failed to reactivate subscription');
} finally {
setIsLoading(false);
}
}
return (
<div className="subscription-manager">
<div className="current-plan">
<h3>Current Plan: {subscription.plan.name}</h3>
<p>Status: {subscription.status}</p>
<p>Renews: {format(subscription.currentPeriodEnd, 'MMM d, yyyy')}</p>
</div>
{subscription.cancelAtPeriodEnd && (
<Alert variant="warning">
Your subscription will end on {format(subscription.currentPeriodEnd, 'MMM d, yyyy')}
<Button onClick={handleReactivate} disabled={isLoading}>
Reactivate
</Button>
</Alert>
)}
<div className="available-plans">
<h4>Available Plans</h4>
{availablePlans.map(plan => (
<PlanCard
key={plan.id}
plan={plan}
isCurrent={plan.priceId === subscription.priceId}
onSelect={() => handleUpgrade(plan.priceId)}
disabled={isLoading}
/>
))}
</div>
{!subscription.cancelAtPeriodEnd && (
<Button variant="destructive" onClick={handleCancel} disabled={isLoading}>
Cancel Subscription
</Button>
)}
</div>
);
}
```
### 5. LemonSqueezy Integration
```typescript
import { lemonSqueezySetup, createCheckout, getSubscription } from '@lemonsqueezy/lemonsqueezy.js';
// Initialize LemonSqueezy
lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY! });
// Create LemonSqueezy checkout
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;
}
// LemonSqueezy webhook handler
async function handleLemonSqueezyWebhook(req: NextApiRequest): Promise<void> {
const signature = req.headers['x-signature'] as string;
const payload = JSON.stringify(req.body);
// Verify signature
const hmac = crypto.createHmac('sha256', process.env.LEMONSQUEEZY_WEBHOOK_SECRET!);
hmac.update(payload);
const expectedSignature = hmac.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid webhook signature');
}
const { event_name, data } = req.body;
switch (event_name) {
case 'subscription_created':
await handleLSSubscriptionCreated(data);
break;
case 'subscription_updated':
await handleLSSubscriptionUpdated(data);
break;
case 'subscription_cancelled':
await handleLSSubscriptionCancelled(data);
break;
case 'order_created':
await handleLSOrderCreated(data);
break;
}
}
```
### 6. Payment Security
```typescript
// PCI-compliant payment handling patterns
// Never log sensitive payment data
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;
}
// Secure API endpoint
async function createPaymentIntent(req: NextApiRequest, res: NextApiResponse) {
// Validate request
const { amount, currency, customerId } = req.body;
if (!amount || amount < 50) { // Minimum $0.50
return res.status(400).json({ error: 'Invalid amount' });
}
// Use idempotency key
const idempotencyKey = req.headers['idempotency-key'] as string;
if (!idempotencyKey) {
return res.status(400).json({ error: 'Idempotency key required' });
}
try {
const paymentIntent = await stripe.paymentIntents.create(
{
amount,
currency: currency || 'usd',
customer: customerId,
automatic_payment_methods: { enabled: true },
metadata: {
userId: req.user.id,
},
},
{ idempotencyKey }
);
// Only return necessary data
res.json({
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id,
});
} catch (error) {
console.error('Payment error:', sanitizeForLogging(error));
if (error.type === 'StripeCardError') {
return res.status(400).json({ error: error.message });
}
res.status(500).json({ error: 'Payment processing failed' });
}
}
// Webhook security middleware
function verifyWebhookSignature(
payload: Buffer,
signature: string,
secret: string
): boolean {
try {
stripe.webhooks.constructEvent(payload, signature, secret);
return true;
} catch {
return false;
}
}
```
## Use Cases
### 1. E-commerce Checkout
```typescript
// Complete e-commerce checkout flow
async function processCheckout(cart: Cart, user: User): Promise<CheckoutResult> {
// Calculate totals
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;
// Create Stripe checkout
const session = await createCheckoutSession(cart.items, user.stripeCustomerId, {
orderId: generateOrderId(),
userId: user.id,
});
// Create pending order
await db.order.create({
data: {
userId: user.id,
status: 'pending',
subtotal,
tax,
shipping,
total,
stripeSessionId: session.id,
items: {
create: cart.items.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.price,
})),
},
},
});
return {
checkoutUrl: session.url,
sessionId: session.id,
};
}
```
### 2. SaaS Subscription
```typescript
// SaaS subscription management
async function handlePlanChange(
userId: string,
newPlanId: string
): Promise<PlanChangeResult> {
const user = await db.user.findUnique({
where: { id: userId },
include: { subscription: true },
});
if (!user.subscription) {
// New 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) {
// Upgrade - charge prorated amount
await updateSubscription(user.subscription.stripeSubscriptionId, newPlanId, 'create_prorations');
return { action: 'upgraded', newPlan };
} else {
// Downgrade - apply at period end
await updateSubscription(user.subscription.stripeSubscriptionId, newPlanId, 'none');
return { action: 'scheduled', effectiveDate: user.subscription.currentPeriodEnd };
}
}
```
## 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
```markdown
## 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
- [Stripe Documentation](https://stripe.com/docs)
- [PayPal Developer](https://developer.paypal.com/)
- [LemonSqueezy Docs](https://docs.lemonsqueezy.com/)
- [PCI DSS Standards](https://www.pcisecuritystandards.org/)
This skill implements enterprise-grade payment processing with Stripe, PayPal, and LemonSqueezy, covering one-time payments, subscriptions, webhooks, refunds, and PCI compliance patterns. It includes checkout flows, subscription lifecycle management, webhook signature verification, and UI components for subscription management. The code samples and patterns are ready to integrate into a JavaScript/TypeScript application.
The skill provides helpers to create checkout sessions and subscriptions, update or cancel subscriptions, and process refunds via Stripe. It includes robust webhook handlers that verify signatures, enforce idempotency, and route events to business logic. PayPal order creation and capture flows are supported via the PayPal SDK, and LemonSqueezy checkout and webhook handling are provided with HMAC verification. Subscription UI components show current plan, upgrades, cancellations, and reactivation flows.
How do webhooks avoid duplicate processing?
The handlers verify signatures, derive an idempotency key from the event ID, check a processed-events store, and skip or acknowledge duplicates.
How can I reduce PCI scope?
Prefer hosted checkout solutions (Stripe Checkout, PayPal Checkout, LemonSqueezy) and never log raw card data on your servers.