plugins/stripe-billing-master/skills/stripe-list-pagination-previous-attributes/SKILL.md
Stripe list-API pagination and event.data.previous_attributes semantics. PROACTIVELY activate for: (1) invoice.lines.data / charge.refunds.data / subscription.items.data embedded pagination, (2) starting_after cursor when falling through to list APIs, (3) has_more flag checking, (4) event.data.previous_attributes field semantics (which fields changed between old and new state), (5) Cumulative vs per-event Stripe fields (charge.amount_refunded is cumulative), (6) Delta computation patterns, (7) Plan resolution from invoice line items with pagination, (8) Safety fallback on pagination exhaustion (G6 — {plan:'free',credits:0}), (9) API error handling mid-scan. Provides: full pagination scan pattern, previous_attributes delta helper, plan resolver with paginated line-item scan.
npx skillsauth add JosiahSiegel/claude-plugin-marketplace stripe-list-pagination-previous-attributesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Field | Semantic |
|--|--|
| event.data.previous_attributes | ONLY the fields that changed; diff the current object against this |
| event.data.previous_attributes.amount_refunded | Previous cumulative refund total — subtract from charge.amount_refunded for per-event delta |
| charge.amount_refunded | CUMULATIVE across all refunds, NEVER per-event |
| invoice.lines.has_more | true -> embedded data is page 1; paginate with starting_after |
| invoice.lines.data.at(-1).id | The cursor for starting_after on the next page |
| stripe.invoices.listLineItems(invoice.id, { starting_after }) | Resumes AFTER the cursor — does NOT re-scan page 1 |
Use whenever:
.data[] array from a Stripe object and might need to paginateRelated skills:
getRefundDelta is consumed in the refund handler: stripe-billing-master:stripe-refund-dispute-lifecycleresolvedVia gates email rendering and audit logging: stripe-billing-master:stripe-credit-audit-trailasync function scanAllLineItems(invoice: Stripe.Invoice): Promise<Stripe.InvoiceLineItem[]> {
const items: Stripe.InvoiceLineItem[] = [...invoice.lines.data];
let cursor = invoice.lines.data.at(-1)?.id;
let hasMore = invoice.lines.has_more;
while (hasMore && cursor) {
const page = await stripe.invoices.listLineItems(invoice.id, {
limit: 100,
starting_after: cursor,
});
items.push(...page.data);
hasMore = page.has_more;
cursor = page.data.at(-1)?.id;
}
return items;
}
Without starting_after, stripe.invoices.listLineItems(invoice.id) re-fetches page 1 — the same page you already have embedded in invoice.lines.data. The scan loops over identical content until has_more never flips, concluding "no match" on lines that never got scanned. Always thread the cursor.
Edge case: if
invoice.lines.datais empty butinvoice.lines.has_moreistrue(rare, but Stripe can return this shape when the embeddedlimitis 0), the loop above never executes becausecursorisundefined. In that case, fall through tostripe.invoices.listLineItems(invoice.id, { limit: 100 })with nostarting_afterto start a fresh scan.
previous_attributes delta helpertype AmountRefundedChanged = { amount_refunded?: number };
export function getRefundDelta(event: Stripe.Event, charge: Stripe.Charge): number | null {
const prev = (event.data.previous_attributes as AmountRefundedChanged | undefined)?.amount_refunded;
if (typeof prev === "number") return charge.amount_refunded - prev;
return null; // caller falls back to embedded / list / skip-revocation
}
export async function resolvePlanFromInvoice(invoice: Stripe.Invoice): Promise<{
plan: Plan;
credits: number;
resolvedVia: "priceMap" | "safetyFallback";
}> {
try {
const items = await scanAllLineItems(invoice); // G3 -- paginate first
for (const item of items) {
const mapped = PRICE_TO_PLAN[item.price?.id ?? ""];
if (mapped) return { ...mapped, resolvedVia: "priceMap" };
}
logEvent("credit_price_resolve_unknown", { invoiceId: invoice.id, lineCount: items.length });
return { plan: "free", credits: 0, resolvedVia: "safetyFallback" }; // G6
} catch (err) {
logEvent("credit_price_resolve_error", { invoiceId: invoice.id, err: String(err) });
return { plan: "free", credits: 0, resolvedVia: "safetyFallback" }; // G6
}
}
The email renderer uses resolvedVia to gate plan names on priceMap — never renders "Welcome to Free" on the safety-fallback path (G-bonus).
development
Use for Clerk sessions, tokens, webhooks, orgs, and security. PROACTIVELY activate for session tokens, JWT templates, getToken(), custom claims, pending sessions, multi-session UX, organizations, roles, permissions, system vs custom permissions, features/plans, MFA/passkeys/password policy/bot protection, Clerk webhooks, Svix signatures, verifyWebhook(), user/org sync, retries/replays, environment variables, custom domains, secret rotation, logs, and auth security reviews. Provides token semantics, webhook idempotency, authorization defaults, and hardening checklist.
tools
Use for Clerk in Next.js. PROACTIVELY activate for @clerk/nextjs setup, App Router auth()/currentUser(), clerkMiddleware(), proxy.ts/middleware.ts, createRouteMatcher(), protected pages/layouts/Route Handlers/Server Actions/API routes/tRPC, auth.protect() role/permission/token checks, ClerkProvider placement, server-only clerkClient, Link prefetch, redirects, 401/404 auth failures, custom domains, __clerk proxy paths, and deployment gotchas. Provides file patterns, server/client boundary rules, matcher templates, and production checks.
development
Use for Clerk frontend auth flows. PROACTIVELY activate for React, JavaScript, Vue, Nuxt, Astro, Expo, React Router, TanStack React Start, or SPA setup; ClerkProvider and publishable-key wiring; SignIn/SignUp/UserButton/UserProfile/OrganizationSwitcher; custom useUser/useAuth/useClerk/useSignIn/useSignUp/useSession/useOrganization flows; multi-session UX; cross-origin getToken() fetches; loading states, redirects, routing, CORS/cookies, or hydration bugs. Provides SDK selection, UI patterns, token-fetch templates, and frontend gotchas.
development
Use for Clerk dev/prod readiness, deployment, and multi-language implementation planning. PROACTIVELY activate for environment variables, pk_test/sk_test vs pk_live/sk_live, local dev, preview/staging/prod instances, domains/DNS, redirects, OAuth credentials, custom domains/proxy, authorizedParties, CSP, CORS/cookies, webhooks/tunnels, Vercel/Netlify/Cloudflare/API gateways, monitoring/troubleshooting, and backends in Node/Express/Fastify, Python/FastAPI/Django/Flask, Go, Ruby/Rails, Java/Spring, .NET, PHP/Laravel. Provides checklists, rollout plans, and language-portable patterns.