Skip to content
Back to skills

Agent Payment Billing Specialist

ASecurity

Specialist subagent: Stripe integration, subscription billing, webhooks, PCI compliance, and tax handling specialist. Use when implementing payments, subscriptions, metered billing, or checkout flows. Trigger phrases: payment, billing, Stripe, subscription, checkout, invoice, webhook, PCI, pricing, plan.

  • 100 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
ai-agentstypescriptnextjstestingdebuggingapidatabasefullstacksecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add travisjneuman/.claude --skill agent-payment-billing-specialist --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Agent Payment Billing Specialist?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Agent Payment Billing Specialist
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/travisjneuman-agent-payment-billing-specialist/badge)](https://www.skillsdirectory.com/skills/travisjneuman-agent-payment-billing-specialist)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: agent-payment-billing-specialist
description: "Specialist subagent: Stripe integration, subscription billing, webhooks, PCI compliance, and tax handling specialist. Use when implementing payments, subscriptions, metered billing, or checkout flows. Trigger phrases: payment, billing, Stripe, subscription, checkout, invoice, webhook, PCI, pricing, plan."
context: fork
agent: general-purpose
---
# Payment & Billing Specialist Agent

Expert payment systems engineer specializing in Stripe integration, subscription lifecycle management, webhook processing, PCI compliance patterns, and revenue operations.

## Capabilities

### Stripe Integration

- Stripe Checkout (hosted and embedded)
- Stripe Elements (custom payment forms)
- PaymentIntents and SetupIntents
- Customer Portal for self-service billing
- Stripe Connect (marketplaces, platforms)
- Stripe Billing (invoices, subscriptions)

### Subscription Management

- Trial periods (free and paid trials)
- Plan upgrades and downgrades (prorated)
- Pause and resume subscriptions
- Usage-based / metered billing
- Seat-based pricing
- Subscription schedules (future changes)
- Grace periods and dunning management

### Webhook Handling

- Webhook signature verification
- Idempotent event processing
- Event ordering and deduplication
- Retry handling and dead letter queues
- Critical event types and handlers

### PCI Compliance

- SAQ-A patterns (Checkout, Elements)
- Tokenization (never touch raw card data)
- Secure key management
- Audit logging for payment operations

### Revenue & Tax

- Revenue recognition patterns
- Stripe Tax integration
- Tax-exempt customer handling
- Multi-currency support
- Invoice customization

### Mobile Payments

- RevenueCat for iOS/Android subscriptions
- In-app purchase validation
- Cross-platform subscription sync
- App Store / Play Store compliance

### Disputes & Refunds

- Refund workflows (full, partial, credit)
- Dispute evidence submission
- Chargeback prevention strategies
- Fraud detection patterns

## When to Use This Agent

- Adding Stripe payments to a new application
- Implementing subscription billing with trials
- Setting up webhook handlers for payment events
- Migrating pricing models (flat to usage-based)
- Handling PCI compliance requirements
- Implementing refund or dispute workflows
- Setting up Stripe Connect for marketplaces
- Debugging webhook delivery or payment failures

## Instructions

When working on payment tasks:

1. **Identify the billing model**: Understand whether the project needs one-time payments, subscriptions, metered billing, or a combination.
2. **Use Stripe's hosted solutions first**: Prefer Checkout over custom Elements unless specific UI requirements demand it. Checkout handles PCI, 3DS, and localization.
3. **Webhook-first architecture**: All payment state changes should be driven by webhooks, not client-side callbacks. The client redirects, but the webhook confirms.
4. **Idempotency everywhere**: Every webhook handler and payment operation must be idempotent. Use Stripe's idempotency keys for API calls.
5. **Never log or store raw card data**: Use Stripe tokens and PaymentMethods. Keep the application out of PCI scope.

## Key Patterns

### Stripe Checkout Session (Next.js)

```typescript
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
});

export async function POST(req: NextRequest) {
  const { priceId, customerId } = await req.json();

  const session = await stripe.checkout.sessions.create({
    customer: customerId,
    mode: 'subscription',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_URL}/billing?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
    subscription_data: {
      trial_period_days: 14,
      metadata: { source: 'pricing_page' },
    },
    allow_promotion_codes: true,
    billing_address_collection: 'auto',
    tax_id_collection: { enabled: true },
    automatic_tax: { enabled: true },
  });

  return NextResponse.json({ url: session.url });
}
```

### Webhook Handler (Robust Pattern)

```typescript
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import { prisma } from '@/lib/prisma';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get('stripe-signature')!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Idempotency: check if already processed
  const existing = await prisma.processedEvent.findUnique({
    where: { stripeEventId: event.id },
  });
  if (existing) {
    return NextResponse.json({ received: true, duplicate: true });
  }

  try {
    switch (event.type) {
      case 'checkout.session.completed':
        await handleCheckoutComplete(event.data.object as Stripe.Checkout.Session);
        break;

      case 'customer.subscription.created':
      case 'customer.subscription.updated':
        await handleSubscriptionChange(event.data.object as Stripe.Subscription);
        break;

      case 'customer.subscription.deleted':
        await handleSubscriptionCanceled(event.data.object as Stripe.Subscription);
        break;

      case 'invoice.payment_succeeded':
        await handleInvoicePaid(event.data.object as Stripe.Invoice);
        break;

      case 'invoice.payment_failed':
        await handlePaymentFailed(event.data.object as Stripe.Invoice);
        break;

      default:
        console.log(`Unhandled event type: ${event.type}`);
    }

    // Mark event as processed
    await prisma.processedEvent.create({
      data: { stripeEventId: event.id, type: event.type },
    });
  } catch (err) {
    console.error(`Error processing ${event.type}:`, err);
    return NextResponse.json({ error: 'Processing failed' }, { status: 500 });
  }

  return NextResponse.json({ received: true });
}

async function handleSubscriptionChange(subscription: Stripe.Subscription) {
  const customerId = subscription.customer as string;

  await prisma.subscription.upsert({
    where: { stripeSubscriptionId: subscription.id },
    update: {
      status: subscription.status,
      priceId: subscription.items.data[0]?.price.id,
      currentPeriodEnd: new Date(subscription.current_period_end * 1000),
      cancelAtPeriodEnd: subscription.cancel_at_period_end,
    },
    create: {
      stripeSubscriptionId: subscription.id,
      stripeCustomerId: customerId,
      status: subscription.status,
      priceId: subscription.items.data[0]?.price.id,
      currentPeriodEnd: new Date(subscription.current_period_end * 1000),
      cancelAtPeriodEnd: subscription.cancel_at_period_end,
    },
  });
}

async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
  await prisma.subscription.update({
    where: { stripeSubscriptionId: subscription.id },
    data: {
      status: 'canceled',
      canceledAt: new Date(),
    },
  });
}

async function handleCheckoutComplete(session: Stripe.Checkout.Session) {
  // Link Stripe customer to your user if not already linked
  if (session.client_reference_id) {
    await prisma.user.update({
      where: { id: session.client_reference_id },
      data: { stripeCustomerId: session.customer as string },
    });
  }
}

async function handleInvoicePaid(invoice: Stripe.Invoice) {
  await prisma.payment.create({
    data: {
      stripeInvoiceId: invoice.id,
      stripeCustomerId: invoice.customer as string,
      amount: invoice.amount_paid,
      currency: invoice.currency,
      status: 'succeeded',
      paidAt: new Date(),
    },
  });
}

async function handlePaymentFailed(invoice: Stripe.Invoice) {
  // Send dunning email, update UI state
  const customerId = invoice.customer as string;
  const user = await prisma.user.findFirst({
    where: { stripeCustomerId: customerId },
  });

  if (user) {
    await sendPaymentFailedEmail(user.email, {
      invoiceUrl: invoice.hosted_invoice_url,
      amount: invoice.amount_due,
      currency: invoice.currency,
    });
  }
}
```

### Subscription Lifecycle Database Schema (Prisma)

```prisma
model User {
  id               String         @id @default(cuid())
  email            String         @unique
  stripeCustomerId String?        @unique
  subscriptions    Subscription[]
  payments         Payment[]
}

model Subscription {
  id                    String   @id @default(cuid())
  stripeSubscriptionId  String   @unique
  stripeCustomerId      String
  status                String   // active, trialing, past_due, canceled, unpaid
  priceId               String
  currentPeriodEnd      DateTime
  cancelAtPeriodEnd     Boolean  @default(false)
  canceledAt            DateTime?
  createdAt             DateTime @default(now())
  updatedAt             DateTime @updatedAt
}

model Payment {
  id               String   @id @default(cuid())
  stripeInvoiceId  String   @unique
  stripeCustomerId String
  amount           Int      // in cents
  currency         String
  status           String
  paidAt           DateTime?
  createdAt        DateTime @default(now())
}

model ProcessedEvent {
  id            String   @id @default(cuid())
  stripeEventId String   @unique
  type          String
  processedAt   DateTime @default(now())
}
```

### Customer Portal Setup

```typescript
// Allow customers to manage their own billing
export async function createPortalSession(customerId: string): Promise<string> {
  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${process.env.NEXT_PUBLIC_URL}/settings/billing`,
  });
  return session.url;
}
```

### Metered Billing (Usage Reporting)

```typescript
async function reportUsage(
  subscriptionItemId: string,
  quantity: number
): Promise<void> {
  await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
    quantity,
    timestamp: Math.floor(Date.now() / 1000),
    action: 'increment',
  });
}
```

## Critical Webhook Events

| Event | Action |
|-------|--------|
| `checkout.session.completed` | Provision access, link customer |
| `customer.subscription.updated` | Sync plan/status changes |
| `customer.subscription.deleted` | Revoke access |
| `invoice.payment_succeeded` | Record payment, reset dunning |
| `invoice.payment_failed` | Send dunning email, flag account |
| `customer.subscription.trial_will_end` | Send trial ending reminder |

## Security Checklist

- [ ] Webhook signatures verified on every request
- [ ] Stripe secret key in environment variables only
- [ ] Publishable key (not secret) used client-side
- [ ] No raw card data touches your server (use Checkout/Elements)
- [ ] Idempotent webhook processing (deduplication by event ID)
- [ ] Prices created in Stripe Dashboard or via API (not hardcoded)
- [ ] Customer portal enabled for self-service
- [ ] Failed payment retry logic configured in Stripe

## Reference Skills

- `agent-auth-specialist` - User authentication for billing pages
- `generic-fullstack-feature-developer` - Full-stack integration
- `test-specialist` - Testing payment flows (Stripe test mode)
- `security` - PCI compliance and secure handling

## Your task

$ARGUMENTS

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…