mcpField
S
stripe-refund-dispute-lifecycle

stripe-refund-dispute-lifecycle

github.com/josiahsiegelDevelopment5979 installsUpdated 19w ago
Repository

About stripe-refund-dispute-lifecycle

A skill for developers implementing Stripe refund and dispute webhook handlers that change credits or paid status. It emphasizes refund-delta calculation, explicit dispute-status handling, and distinguishing subscription refunds from credit-pack refunds.

Key features

  1. 01Refund-delta source priority
  2. 02Dispute status restore allowlist
  3. 03Credit-pack proportional revocation
  4. 04Subscription and payment-mode distinction

Use cases

  1. 01Implement Stripe refund webhook handlers
  2. 02Handle dispute creation and closure
  3. 03Revoke credits proportionally after refunds

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill stripe-refund-dispute-lifecycle

After installing, type/stripe-refund-dispute-lifecyclein Claude Code to confirm it loaded.

Details

From the project README

Quick Reference

EventActionKey rule
charge.refundedRevoke credits proportional to refund deltaG2 — previous_attributes.amount_refunded
charge.dispute.createdSet user past_due + store checkpointG1 + G9
charge.dispute.closedwon / warning_closed / prevented -> restore; lost -> no-op (charge.refunded handles); else -> no-opG5 + G7
Refund source priorityWhen to use
event.data.previous_attributes.amount_refunded (G2)Primary — always prefer
charge.refunds.data (sorted by created desc)Fallback when previous_attributes absent
stripe.refunds.list({charge, limit:1})Last resort when embedded missing
charge.amount_refunded aloneNEVER (cumulative, not per-event)

When to Use This Skill

Use when implementing any handler that revokes credits or mutates a paid-status column in response to Stripe refund or dispute events.

Related skills:

  • For getRefundDelta (the G2 delta helper): stripe-billing-master:stripe-list-pagination-previous-attributes
  • For the canonical refund helper and audit-row invariant: stripe-billing-master:stripe-credit-audit-trail
  • For the G1 checkpoint pattern every dispute handler wraps: stripe-billing-master:stripe-webhook-idempotency

Core Rules

G2: refund delta

Use getRefundDelta() from stripe-billing-master:stripe-list-pagination-previous-attributes — that skill owns the delta-computation helper. Key guarantee: the helper returns null when no source is available, and the handler MUST skip revocation rather than guess.

G7: exhaustive shouldRestoreStatus
const shouldRestoreMap = {
  won: true,
  warning_closed: true,
  prevented: true,
  lost: false,
  needs_response: false,
  under_review: false,
  warning_needs_response: false,
  warning_under_review: false,
  charge_refunded: false,
} satisfies Record<Stripe.Dispute.Status, boolean>;

export const shouldRestoreStatus = (s: Stripe.Dispute.Status): boolean => shouldRestoreMap[s];

When Stripe adds a new status in a future SDK version, this object is a compile error until you add the key — forcing a conscious G5 allowlist decision.

Credit-pack vs subscription refund
async function resolveCreditsToRevoke(charge: Stripe.Charge, refundAmount: number) {
  const sessions = await stripe.checkout.sessions.list({
    payment_intent: charge.payment_intent as string,
    limit: 1,
    expand: ["data.line_items"],
  });
  const session = sessions.data[0];
  if (session?.mode !== "subscription") {
    const pack = CREDIT_PACKS.find(p => session?.line_items?.data?.[0]?.price?.id === p.priceId);
    if (pack && session.amount_total && session.amount_total > 0) {
      // Proportional revocation: if they refunded 50% of the pack, revoke 50% of the credits
      return Math.round(pack.credits * (refundAmount / session.amount_total));
    }
  }
  // Subscription: 1 credit = 1 cent at cash-equivalent
  return refundAmount;
}

Notes on the credit-pack math: proportional revocation matters because packs are bulk-priced (e.g., 1000 credits for $9 instead of $10) — a flat refundAmount -> credits conversion over-revokes. Always look up the originating Checkout Session to distinguish mode: "subscription" (cash-equivalent) from mode: "payment" with a known pack price ID (proportional).

Comments

Markdown supported