🌱stripe.billing.context

paste to any AI agent

assumes

- you're integrating Stripe (Checkout, Subscriptions, or Webhooks). this seed isn't useful for general payments-domain advice independent of the Stripe API. - you have a Stripe account (test mode is fine) or are willing to create one. live current prices and IDs are fetched from your dashboard, not cached here. - a webhook endpoint is configurable in your stack. on serverless platforms without that surface, the recommendations need adapting. - you're working with Stripe API versions current at audit time. check the changelog if behaviour you saw documented somewhere doesn't match what the API returns. Stripe integration context for agents. What to know before writing a line of billing code. **Before assuming any parameter name or response shape: check the Stripe API version in use.** Stripe versions by date (e.g. `2023-10-16`). Your SDK may default to an older version than the docs you're reading. The version is set on your API key in the Dashboard or per-request via the `Stripe-Version` header. Mismatches are silent — wrong field names don't error, they just return `null`. This bundle is orientation, not reference. Fetch live docs from `sources.md` for current parameter names and response shapes. The concepts here are stable; the exact field names are not. ---

view raw
# Usage: curl -sSL https://seed.show/stripe.billing.context | bash -s <install-path>
# <install-path> is the directory where the file should land.

set -euo pipefail
# seed.show — assumption contract for stripe.billing.context
# (printed to stderr before unpack so the agent sees it in tool output)
cat >&2 <<'ASSUMPTIONS_EOF'
- you're integrating Stripe (Checkout, Subscriptions, or Webhooks). this seed isn't useful for general payments-domain advice independent of the Stripe API.
- you have a Stripe account (test mode is fine) or are willing to create one. live current prices and IDs are fetched from your dashboard, not cached here.
- a webhook endpoint is configurable in your stack. on serverless platforms without that surface, the recommendations need adapting.
- you're working with Stripe API versions current at audit time. check the changelog if behaviour you saw documented somewhere doesn't match what the API returns.

Stripe integration context for agents. What to know before writing a line of billing code.

**Before assuming any parameter name or response shape: check the Stripe API version in use.** Stripe versions by date (e.g. `2023-10-16`). Your SDK may default to an older version than the docs you're reading. The version is set on your API key in the Dashboard or per-request via the `Stripe-Version` header. Mismatches are silent — wrong field names don't error, they just return `null`.

This bundle is orientation, not reference. Fetch live docs from `sources.md` for current parameter names and response shapes. The concepts here are stable; the exact field names are not.

---

ASSUMPTIONS_EOF

[ -z "${1:-}" ] && {
  echo "install requires a path: curl -sSL https://seed.show/stripe.billing.context | bash -s <install-path>" >&2
  exit 1
}
TARGET="$1"
mkdir -p "$TARGET"
DEST="$TARGET/seed-fold.yRg2Pa.folded.md"

cat > "$DEST" <<'PORTDOWN_2459CE42'
<!--
This is a `.folded.md` archive — a directory packed into one markdown
file. The markers below are load-bearing; don't edit them directly.

To unpack (requires bash — if you have no shell, tell the user):
  1. curl -sSLf https://fold.dom.vin/skill | bash -s <INSTALL_DIR>
  2. <INSTALL_DIR>/fold/scripts/unfold <this-file>
     (or: unfold <this-file>  if fold/scripts is on your PATH)
-->

---
fold: true
marker: 68605f
at: 2026-05-07T16:16:25Z
root: seed-pack.UL8Hkz
---

<!--fold:68605f@file path="README.md" mode="644"-->
# stripe.billing.context


## Assumptions

- you're integrating Stripe (Checkout, Subscriptions, or Webhooks). this seed isn't useful for general payments-domain advice independent of the Stripe API.
- you have a Stripe account (test mode is fine) or are willing to create one. live current prices and IDs are fetched from your dashboard, not cached here.
- a webhook endpoint is configurable in your stack. on serverless platforms without that surface, the recommendations need adapting.
- you're working with Stripe API versions current at audit time. check the changelog if behaviour you saw documented somewhere doesn't match what the API returns.

Stripe integration context for agents. What to know before writing a line of billing code.

**Before assuming any parameter name or response shape: check the Stripe API version in use.** Stripe versions by date (e.g. `2023-10-16`). Your SDK may default to an older version than the docs you're reading. The version is set on your API key in the Dashboard or per-request via the `Stripe-Version` header. Mismatches are silent — wrong field names don't error, they just return `null`.

This bundle is orientation, not reference. Fetch live docs from `sources.md` for current parameter names and response shapes. The concepts here are stable; the exact field names are not.

---

## The object model

Stripe's data model has a specific hierarchy. Understanding it prevents the most common architectural mistakes:

```
Customer
  └── PaymentMethod (attached, can have many)
  └── Subscription
        └── SubscriptionItem (one per Price)
        └── Invoice (generated per billing cycle)
              └── PaymentIntent (one per Invoice requiring payment)
```

**Customer** — the entity being billed. Created once, reused forever. Attach PaymentMethods here, not to individual payments.

**PaymentMethod** — the payment instrument (card, bank account, etc). Created client-side with Stripe.js, attached to a Customer server-side. Never handle raw card data.

**Product** — what you're selling (description only). Reusable across prices.

**Price** — one billing configuration: amount, currency, recurrence interval. A Product can have many Prices (monthly/annual, different currencies, different tiers). Subscriptions attach to a Price, not a Product.

**Subscription** — the recurring billing contract. Generates Invoices on each billing cycle. Has its own lifecycle: `trialing` → `active` → `past_due` → `canceled`.

**Invoice** — the bill for a billing period. `status: draft` → `open` (sent, payment attempted) → `paid` or `uncollectible`. Each subscription invoice triggers payment automatically.

**PaymentIntent** — represents one payment attempt. Created automatically for invoices. Confirmed client-side when 3DS authentication is required.

The event-driven architecture flows from this model: Stripe emits events at each state transition. Your webhook handler is the authoritative source of truth for subscription status — not the API response from creating a subscription.

---

## What agents get wrong: the 5 classic mistakes

**1. Idempotency keys are required for all POST requests that create objects.**
Pass `Idempotency-Key: <uuid>` on payment, charge, and subscription creation. Without it, network retries create duplicate charges. Reuse the same key on retry for the same logical operation; generate a new one for a new operation.

**2. Webhook signatures must be verified before trusting the payload.**
Stripe signs every webhook with your endpoint secret. Verify with `stripe.webhooks.constructEvent(body, sig, secret)`. The `body` must be the raw request bytes — not parsed JSON. Verifying after `JSON.parse` breaks HMAC validation silently: the bytes no longer match.

**3. Price ≠ Product.**
A Product is a reusable description of what you sell. A Price is one billing configuration (amount, currency, recurrence). Subscriptions attach to a `price_id`, not a `product_id`. Getting these backwards creates subscriptions with no billing.

**4. Use test clocks for subscription lifecycle testing.**
You cannot fast-forward real time to test trial expiry, renewal, dunning, or cancellation. `stripe.testHelpers.testClocks.create()` lets you advance time in test mode and observe the resulting events. Without test clocks, you cannot fully test the subscription lifecycle.

**5. The Customer Portal handles subscription management — don't rebuild it.**
Stripe's hosted Customer Portal handles payment method updates, invoice history, cancellation, and plan changes. Unless the product requires deeply custom UI, the Portal is correct. Create a portal session server-side and redirect. Rebuilding it means rebuilding Stripe's edge cases too.

---

## What to never do

- Pass amounts in dollars. Stripe amounts are always in the smallest currency unit (cents for USD). `$10.00` = `1000`. This is the most common off-by-100x error.
- Confirm a PaymentIntent server-side when 3DS is required — handle it client-side with Stripe.js. Server-side confirmation blocks 3DS and causes payment failures.
- Store raw card data. Stripe handles PCI compliance; you handle only the `payment_method` ID.
- Assume `payment_intent.succeeded` means the subscription is active. Subscriptions have their own event lifecycle. Listen to `customer.subscription.updated` and `invoice.paid` for subscription state.
- Use `customer.subscription.deleted` as the only cancellation signal. It fires at period end for cancellations scheduled with `cancel_at_period_end: true` — not at the moment the customer cancels.

---

## Test vs. live mode

Test and live mode are completely separate environments with separate API keys, separate data, and separate webhook endpoints. Switching from `sk_test_...` to `sk_live_...` does not migrate data.

Test cards: `4242 4242 4242 4242` always succeeds. `4000 0000 0000 9995` always declines. `4000 0027 6000 3184` triggers 3DS authentication. Full list: https://stripe.com/docs/testing

---

## What AI is changing

**Where AI is helping in billing:**

- **Usage-based billing automation** — LLMs can interpret ambiguous usage logs and map them to billing meters, reducing manual categorization work
- **Dunning optimization** — ML models predict optimal retry timing for failed payments based on card network patterns; Stripe Adaptive Acceptance does this natively
- **Fraud detection** — Stripe Radar uses ML; agents can tune Radar rules or evaluate whether a custom model is worth the complexity vs. trusting Radar
- **Churn prediction from payment patterns** — payment decline sequences (`payment_intent.payment_failed` events) are early churn signals; agents can build alerting pipelines on top of them
- **Invoice reconciliation** — agents can match Stripe invoice line items against internal ledgers at a scale that was previously manual

**What stays human:**

- Pricing strategy — the right price point, the right tier boundaries, whether to offer monthly/annual. No model knows your customers' willingness to pay.
- Dispute resolution — Stripe can surface the data, but winning a chargeback requires human judgment and documentation
- Tax configuration — Stripe Tax handles mechanics, but determining nexus and taxable product classification requires a human (or a tax advisor) making risk calls
- Dunning policy — how many retries, how long to wait, when to cancel: these are business decisions with customer relationship implications
<!--fold:68605f@file path="patterns.md" mode="644"-->
# implementation patterns

Common Stripe integration flows. These are reference patterns — fetch current parameter names from the API docs (see `sources.md`).

---

## Subscription creation

The minimal correct flow:

1. **Client:** Collect card with Stripe.js → `stripe.createPaymentMethod()` → send `paymentMethodId` to your server
2. **Server:** Create or retrieve a Customer (idempotent — store the `customer_id` in your DB after first creation)
3. **Server:** Attach the PaymentMethod to the Customer
4. **Server:** Create the Subscription with `default_payment_method` and `expand: ['latest_invoice.payment_intent']`
5. **Server:** Return the subscription status and `client_secret` if the PaymentIntent status is `requires_action`
6. **Client:** If `requires_action`, call `stripe.confirmCardPayment(client_secret)` to handle 3DS

Do not confirm the PaymentIntent server-side if 3DS may be required. The `payment_intent.status` field after subscription creation tells you whether client-side confirmation is needed.

Idempotency key: use a stable key tied to the user + price (e.g. `create-sub-{userId}-{priceId}`) so retries don't create duplicate subscriptions.

---

## Plan upgrade / downgrade

Stripe's proration logic handles mid-cycle plan changes:

1. Retrieve the Subscription and its SubscriptionItem ID
2. Call `stripe.subscriptions.update()` with the new `price` on the existing item
3. Set `proration_behavior`: `create_prorations` (default, adds proration line items to next invoice), `always_invoice` (immediately invoices the difference), or `none` (switches with no proration)
4. For downgrades, you may want `billing_cycle_anchor: 'unchanged'` to keep the billing date consistent

Preview before charging: `stripe.invoices.retrieveUpcoming()` returns the next invoice including prorations before you commit the change. Show this to the customer before confirmation.

---

## Payment failure handling (dunning)

When `invoice.payment_failed` fires:

1. Check `invoice.attempt_count` to know which retry this is
2. Check `invoice.next_payment_attempt` for when Stripe will retry (controlled by your Smart Retries or custom retry schedule in the Dashboard)
3. Check `subscription.status` — `past_due` means retries are ongoing; `unpaid` means retries exhausted
4. Send your own email with a link to update their payment method (or a Customer Portal session URL)
5. Do not cancel the subscription yourself unless you've decided retries are done — Stripe's retry logic runs independently

On `invoice.paid` after a failure: restore access, clear any dunning flags in your DB.

Smart Retries (Stripe Adaptive Acceptance) is on by default and uses ML to pick optimal retry times. Don't override it with a naive fixed-interval schedule unless you have a reason.

---

## Webhook event handling

The webhook handler is the authoritative source of subscription state. Structure:

1. Verify the signature (raw body + `Stripe-Signature` header + endpoint secret). Any error here: return 400.
2. Extract `event.type` and `event.data.object`
3. Handle idempotently — Stripe may deliver the same event more than once. Check if you've already processed this `event.id` before writing to your DB.
4. Return 200 quickly. Do slow work (email, provisioning) async. Stripe retries if it doesn't get a 200 within a few seconds.

Events to handle for a subscription product:

| Event | Action |
|---|---|
| `customer.subscription.created` | Provision access |
| `invoice.paid` | Confirm active, record period |
| `invoice.payment_failed` | Begin dunning sequence |
| `customer.subscription.updated` | Sync plan/status changes |
| `customer.subscription.deleted` | Revoke access |
| `customer.subscription.trial_will_end` | Send trial-ending warning |

Do not rely on the API response at subscription creation time as the only signal. The webhook is the ground truth; the API response is an optimistic preview.

---

## Trial periods

Two ways to configure trials:

**At subscription creation:** set `trial_period_days` (or `trial_end` as a timestamp). No payment required during trial. `subscription.status` is `trialing` until trial ends.

**With a payment method collected upfront:** set `trial_period_days` and `payment_behavior: 'default_incomplete'`. Collects a payment method and $0 authorization, but charges only when the trial converts.

To test: create a test clock, attach it to a Customer before creating the Subscription, then advance the clock past the trial end. Observe `customer.subscription.trial_will_end` (3 days before) and `customer.subscription.updated` (trial → active, with the first invoice created).

`trial_will_end` fires 3 days before expiry by default. Handle it to send a warning email. This cannot be changed per-subscription — configure the global advance notice in Dashboard settings.

---

## Customer Portal session

```
const session = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: 'https://yourapp.com/account',
});
redirect(session.url);
```

The portal URL is single-use and short-lived. Do not cache it. Create a new session each time the customer clicks "Manage subscription."

Configure what the portal allows in the Dashboard (cancel, upgrade, payment method update, etc.) — not in code. The portal configuration is a Dashboard-level setting, not a per-session parameter.
<!--fold:68605f@file path="sources.md" mode="644"-->
# sources

Fetch these at task time. Check the API version in use before trusting parameter names.

**API version check first:** Your project's Stripe version is visible in the Dashboard → Developers → API keys, or in responses via the `Stripe-Version` header. The docs default to the latest version; your SDK may not.

---

1. **Stripe API reference** — canonical source for objects, endpoints, parameters, errors. All field names are version-specific.
   https://stripe.com/docs/api

2. **Webhook events** — full list of event types and payload shapes:
   https://stripe.com/docs/api/events/types

3. **API versioning** — how versions work, what changes between them, upgrade guide:
   https://stripe.com/docs/upgrades

4. **Idempotency** — how keys work, retry semantics, when to generate a new key:
   https://stripe.com/docs/idempotency

5. **Subscription lifecycle** — states, transitions, invoice events, trial handling, cancellation timing:
   https://stripe.com/docs/billing/subscriptions/overview

6. **Customer Portal** — setup, configuration, session creation, what it handles out of the box:
   https://stripe.com/docs/customer-management

7. **Testing** — test cards, test clocks, test bank accounts, webhook testing:
   https://stripe.com/docs/testing

8. **Stripe CLI** — local webhook forwarding, event replay, fixture-based testing:
   https://stripe.com/docs/stripe-cli

9. **Stripe Radar** — fraud rules, review queues, Adaptive Acceptance:
   https://stripe.com/docs/radar

10. **Usage-based billing** — meters, billing meters API, usage records:
    https://stripe.com/docs/billing/subscriptions/usage-based
<!--fold:68605f@end-->
PORTDOWN_2459CE42

# ── post ──
MARKER=$(awk '/^---$/ { f++; if (f==2) exit; next } f==1 && /^marker:[[:space:]]/ { sub(/^marker:[[:space:]]+/, ""); print; exit }' "$DEST")
[ -z "$MARKER" ] && { echo "seed: archive has no marker — corrupt" >&2; exit 1; }
awk -v m="$MARKER" -v outdir="$TARGET" '
  BEGIN {
    # Match <!--fold:<m>@file path="X"--> with an optional mode attr after
    # the path (fold emits  mode="644"  on executables).
    file_re = "^<!--fold:" m "@file path=\"([^\"]+)\"( mode=\"[0-9]+\")?-->$"
    end_re  = "^<!--fold:" m "@end-->$"
  }
  $0 ~ end_re { if (current) close(current); exit }
  $0 ~ file_re {
    if (current) close(current)
    line = $0
    sub(/^<!--fold:[^@]+@file path="/, "", line); sub(/".*$/, "", line)
    current = outdir "/" line
    dir = current; sub(/\/[^\/]*$/, "", dir)
    if (dir != current) system("mkdir -p \"" dir "\"")
    printf "" > current
    next
  }
  current { print >> current }
' "$DEST"
SEED_EXTRACTED=$(find "$TARGET" -type f -not -path "$DEST" 2>/dev/null | wc -l)
if [ "$SEED_EXTRACTED" = "0" ]; then
  echo "seed: archive contained no files — refusing to delete the source" >&2
  echo "  archive preserved at: $DEST" >&2
  exit 1
fi
rm -f "$DEST"

echo "" >&2
echo "✓ seed unpacked → $TARGET ($SEED_EXTRACTED files)" >&2
find "$TARGET" -type f | sort | while IFS= read -r _sf; do
  echo "  ${_sf#${TARGET}/}" >&2
done
echo "" >&2
if [ -f "$TARGET/SKILL.md" ]; then
  echo "This seed contains a skill (SKILL.md). Install it in your agent's skills directory." >&2
  echo "" >&2
fi
echo "Install the seed skill if not already installed:" >&2
echo "  https://seed.show/skill" >&2
echo "" >&2
echo "Publisher prompt:" >&2
sed 's/^/  /' >&2 <<'__SEED_PROMPT_END_AC1F2B__'
Stripe billing context for agents: object model (customer → payment method → subscription → invoice → payment intent), the 5 classic mistakes, implementation patterns for subscription creation / upgrade / dunning / webhooks / trials, and what AI is changing in billing. Check API version before trusting parameter names.
__SEED_PROMPT_END_AC1F2B__
exit 0

instructions

Stripe billing context for agents: object model (customer → payment method → subscription → invoice → payment intent), the 5 classic mistakes, implementation patterns for subscription creation / upgrade / dunning / webhooks / trials, and what AI is changing in billing. Check API version before trusting parameter names.

idstripe.billing.context size18.6 KB created2026-05-06 expirespermanent