What was straightforward
The core payment primitives. Creating a customer, creating a PaymentIntent, confirming it, reading the result — these were each a single API call with predictable request/response shapes. The API is consistent: every object has an id, creation always returns the full object, and status is always a named string (requires_payment_method, succeeded, etc.). Once you've seen one Stripe object you can read any of them.
Error responses. This was genuinely impressive. The invalid currency error returned the complete list of valid currencies inline in the message — that's the error response equivalent of self-documenting code. The resource_missing error quoted the exact bad ID back. The card decline gave you code, decline_code, advice_code, and network_decline_code as separate fields, which maps cleanly to different audiences (show message to the user, log decline_code for your backend, act on advice_code for retry logic). I didn't have to guess at any of these — the error told me what was wrong and what to do.
Cleanup via API. Customer deletion and product/price archiving both worked in one call each, and the responses immediately confirmed the state change. Nothing required going to the Dashboard.
Where I worked around something
The declined card documentation. This was the most visible friction point. I made seven separate WebFetch attempts against docs.stripe.com/testing trying to get the declined card table, and every response came back incomplete — partly German-localized, partly cut off before the relevant table. The page is dynamically rendered JavaScript and the fetch tool was getting a shell with missing content. I ended up using pm_card_chargeDeclined, a server-side test fixture I knew exists at the API level, and confirmed it worked by running it. That worked fine — but the path was: try docs repeatedly, fail, fall back to a token I couldn't verify from docs, try it empirically. That's a workaround, not the intended path.
The Product/Price model for a one-off charge. You asked me to create a product called "Usability Audit" priced at $99, which I did — but technically that step had nothing to do with the actual payment. The PaymentIntent only needed amount=9900 and currency=usd. The Product and Price objects are Stripe's billing catalog, designed for subscription management and invoicing. They're never referenced by the PaymentIntent. I built the catalog because the task asked for it, but if someone is reading this session to learn how to charge a card, the Product/Price step could mislead them into thinking it's required when it isn't.
The test confirmation flow doesn't reflect production. Confirming a PaymentIntent with pm_card_chargeDeclined or pm_card_visa is a server-side shortcut that only works in test mode. In production, card details are collected client-side by Stripe.js and never touch your server. The workflow we demonstrated — create PI on server, confirm on server with a test PM token — short-circuits the frontend entirely. It's valid for testing the API surface, but it creates a gap: the session shows a complete flow that you can't actually replicate in live mode without building the frontend piece.
PaymentIntents are permanent. When cleaning up, I had to note that three PaymentIntents couldn't be deleted. This is by design (audit trail), but it creates a gap between the user's mental model ("clean up test data") and what's actually possible ("most things, but not transaction records"). There's no API endpoint for it and no in-session workaround — I had to escalate to "the only way is a new account."
What would have made this easier
Static-renderable docs. If docs.stripe.com/testing had a plain markdown or text version with the full tables intact, the declined card investigation would have been a single fetch instead of seven failed ones. Stripe does have llms.txt at the docs root, which was a good index, but it pointed to pages whose content wasn't actually fetchable. An llms-full.txt with the actual reference tables inline would have solved this.
Clearer separation between "charge a card" and "manage a product catalog." The docs path for one-off payments leads you through Payment Intents cleanly, but the framing of "create a product priced at $99" implied catalog setup was part of the charge flow. A note like "for one-off charges you only need amount and currency — Products and Prices are optional catalog metadata" would avoid that confusion.
A way to bulk-delete test data. Stripe has a test clock feature for subscription testing, but no equivalent "teardown this test session" mechanism. For anyone running integration tests repeatedly, the accumulation of permanent PaymentIntent records is noise. Even a test-mode-only delete endpoint for PIs would close that gap cleanly.