@josiahsiegel/stripe-refund-dispute-lifecycle

|

View in AI SkillSafe app
0 downloads
0 stars
0 demos
SKILL.md
namestripe-refund-dispute-lifecycle
description|

Quick Reference

Event Action Key rule
charge.refunded Revoke credits proportional to refund delta G2 — previous_attributes.amount_refunded
charge.dispute.created Set user past_due + store checkpoint G1 + G9
charge.dispute.closed won / warning_closed / prevented -> restore; lost -> no-op (charge.refunded handles); else -> no-op G5 + G7
Refund source priority When 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 alone NEVER (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).

Embed badges

Add these to your README to show the skill's verification status.

SkillSafe verified badge
Verified badge
[![SkillSafe verified badge](https://api.skillsafe.ai/v1/badge/@josiahsiegel/stripe-refund-dispute-lifecycle/verified)](https://skillsafe.ai/skill/@josiahsiegel/stripe-refund-dispute-lifecycle/)
Installs badge
Installs badge
[![Installs badge](https://api.skillsafe.ai/v1/badge/@josiahsiegel/stripe-refund-dispute-lifecycle/installs)](https://skillsafe.ai/skill/@josiahsiegel/stripe-refund-dispute-lifecycle/)
Scan badge
Scan badge
[![Scan badge](https://api.skillsafe.ai/v1/badge/@josiahsiegel/stripe-refund-dispute-lifecycle/scan)](https://skillsafe.ai/skill/@josiahsiegel/stripe-refund-dispute-lifecycle/)
Eval pass rate badge
Eval pass rate
[![Eval pass rate badge](https://api.skillsafe.ai/v1/badge/@josiahsiegel/stripe-refund-dispute-lifecycle/eval)](https://skillsafe.ai/skill/@josiahsiegel/stripe-refund-dispute-lifecycle/)