Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
josiahsiegel avatar

Stripe List Pagination Previous Attributes

  • 52 installs
  • 50 repo stars
  • Updated June 18, 2026
  • josiahsiegel/claude-plugin-marketplace

Helps with ai & agent building tasks.

About

stripe-list-pagination-previous-attributes is a Claude Code skill in the AI & Agent Building category.

  • stripe-list-pagination-previous-attributes
  • AI & Agent Building
  • AI-coding skill

Stripe List Pagination Previous Attributes by the numbers

  • 52 all-time installs (skills.sh)
  • +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #7,142 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill stripe-list-pagination-previous-attributes

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs52
repo stars50
Last updatedJune 18, 2026
Repositoryjosiahsiegel/claude-plugin-marketplace

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Quick Reference

FieldSemantic
event.data.previous_attributesONLY the fields that changed; diff the current object against this
event.data.previous_attributes.amount_refundedPrevious cumulative refund total — subtract from charge.amount_refunded for per-event delta
charge.amount_refundedCUMULATIVE across all refunds, NEVER per-event
invoice.lines.has_moretrue -> embedded data is page 1; paginate with starting_after
invoice.lines.data.at(-1).idThe 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

When to Use This Skill

Use whenever:

  • You read an embedded .data[] array from a Stripe object and might need to paginate
  • You compute a "what changed" delta from a webhook event
  • You resolve a plan / SKU / entitlement from invoice line items or subscription items

Related skills:

  • Where getRefundDelta is consumed in the refund handler: stripe-billing-master:stripe-refund-dispute-lifecycle
  • Where resolvedVia gates email rendering and audit logging: stripe-billing-master:stripe-credit-audit-trail

Pagination scan pattern

async 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.data is empty but invoice.lines.has_more is true (rare, but Stripe can return this shape when the embedded limit is 0), the loop above never executes because cursor is undefined. In that case, fall through to stripe.invoices.listLineItems(invoice.id, { limit: 100 }) with no starting_after to start a fresh scan.

previous_attributes delta helper

type 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
}

Plan resolver with pagination + G6 safety fallback

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).

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.