feat: add stripe service emulator#4
Conversation
|
@mvanhorn is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
ctate
left a comment
There was a problem hiding this comment.
Thanks for this excellent contribution! The Stripe emulator is a strong addition — stateful persistence and webhook delivery address real gaps that stripe-mock leaves open, and the code quality (pagination, expand[], cascading deletes) matches the existing plugins well.
Before merging, there are two things to address:
1. Bug: Checkout UI crashes when session has line items
The checkout page (checkout-sessions.ts:93) reads li.price_id, but line items arrive from the API with a price field (matching Stripe's real API format). When a session has line items, the price lookup fails and price.currency.toUpperCase() throws:
Cannot read properties of undefined (reading 'replace')
Fix: either map price → price_id on insert (line 41), or read li.price instead of li.price_id in the render logic.
2. Inline styles break design consistency
The checkout page uses 6 inline style="..." attributes with a custom green accent (#22c55e), while every other emulator (GitHub, Google, Vercel) uses zero inline styles and the shared amber/gold accent (#fbbf24) from the core stylesheet. These should be extracted into CSS classes in the core UI system to stay consistent with the project's design patterns.
Everything else looks great — tests pass (82/82), build is clean, and the plugin structure follows the established patterns well. Once those two items are addressed this is good to go!
|
Both fixed in e8c5b0f:
Also fixed the unawaited Updated checkout: |
|
Actually sorry @mvanhorn - this branch is behind main. The design system was completely overhauled - from a dark-gray-with-amber look to a black-with-green-terminal aesthetic using pixel fonts |
e8c5b0f to
ff5aab4
Compare
|
Nice! Small nit: the "Cancel" button doesn't match the design system |
|
hey @ctate can we have it deployed and reviewed :) |
|
@mvanhorn Do you mind rebasing again using the new @\emulators scope instead of @\internal? |
Rebased onto main and migrated from @internal to @emulators scope. Registered stripe in SERVICE_REGISTRY. All 17 stripe tests + full suite passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5abd8d7 to
b48f806
Compare
|
Rebased onto main in b48f806. Moved everything from |
ctate
left a comment
There was a problem hiding this comment.
Thanks for the updates!
Changes requested
Bug: Charge metadata is silently dropped
When a charge is created from a confirmed payment intent, metadata is hardcoded to {} instead of copying from the payment intent. This means any webhook handler reading charge.metadata gets empty data. Should be metadata: updated.metadata.
Input validation: line_items is unvalidated
line_items is cast as any[] with no structural validation. Malformed input (missing price or quantity) is silently accepted and will blow up when rendering the checkout page. Please add basic shape validation.
Foreign key validation is missing
Prices accept non-existent product IDs, payment intents accept non-existent customer IDs, etc. Real Stripe returns 400 for these. An emulator that's more permissive than production gives false confidence — code passes locally then breaks in prod. Please validate foreign keys and return Stripe-style errors.
- Copy payment intent metadata to charge on confirm (was hardcoded {})
- Validate line_items shape: require price (string) and quantity (>0)
- Validate line_items price IDs exist in store
- Validate customer IDs on payment intents and checkout sessions
- Validate product IDs on prices
- Return Stripe-style 400 errors for invalid references
|
Fixed in 113c6a4:
|
|
Thank you @mvanhorn! |
Merge upstream/main into feat/stripe-emulator, combining the new okta emulator (from vercel-labs#32) with the stripe emulator in the service registry's SERVICE_NAME_LIST.
Merge upstream/main into feat/stripe-emulator, incorporating the resend emulator (vercel-labs#7) and microsoft v1 OAuth updates (vercel-labs#30) alongside the stripe emulator in the service registry and package dependencies.
|
woohoo |
|
It is released ? i dont see it in the doc and readme |
|
Thanks for merging both emulators. Let me know if you want any adjustments to the stripe or resend implementations. |
|
Appreciate the merge! |



Summary
Adds a Stripe service emulator with stateful persistence, cursor-based pagination, expand[] support, webhook dispatch on every state change, and a checkout UI. Covers the core payment flow: customers, payment intents, charges, products, prices, and checkout sessions.
Why this matters
Stripe's official stripe-mock (1,600 stars) is stateless - data sent via POST is validated but not persisted. Users describe it as "kind of useless for black box testing without some degree of persistence" (24 comments, open since inception). It has no webhook support, and Stripe's own rate limits docs say "do not recommend load testing using the Stripe API in testing environments."
emulate's
Storegives us stateful persistence andWebhookDispatchergives us webhook delivery - the two features stripe-mock is missing.Changes
Plugin structure -
packages/@internal/stripe/with 6 entity types, 5 route files, seed config, checkout UIStateful payment flow:
Cursor-based pagination - All list endpoints support
starting_after,ending_before,limit,created[gte],created[lte]via sharedstripeList()helper. Returns{ object: "list", has_more, data }.expand[] support - Charges expand
customerandpayment_intent. Payment intents expandcustomer. Prices expandproduct. UsesapplyExpand()helper.Stripe-format errors - Returns
{ error: { type, code, message } }instead of generic error responses. Codes includeresource_missing,payment_intent_unexpected_state.11 webhook events - Dispatched on every state change:
customer.created/updated/deleted,payment_intent.created/succeeded/canceled,charge.succeeded,product.created,price.created,checkout.session.completed/expired.Cascading customer deletion - Deleting a customer nullifies
customer_idon related payment intents, charges, and checkout sessions.Form-urlencoded support -
parseStripeBody()handles both JSON andapplication/x-www-form-urlencodedwith bracket notation (metadata[key]=value).Checkout UI
Uses the shared
renderCardPagesystem from@internal/core:API flow
Testing
17 vitest tests covering: CRUD, form-urlencoded, Stripe error format, cursor pagination, email/status filtering, expand[] on payment intents and prices, cascading customer delete, payment flow, checkout sessions, and seed config. All 82 tests across all packages pass.
Scope
Covers the core payment flow with quality matching the existing GitHub plugin (pagination, filtering, webhooks on state changes, cascading operations, expand[]). Subscriptions, invoices, disputes, Connect, and refunds are left for follow-up PRs.
This contribution was developed with AI assistance (Claude Code).