neilberkman

neilberkman

PaperTiger - a stateful mock Stripe server for Elixir testing

We just published PaperTiger, a mock Stripe server we built to solve real pain points in our test suite.

The Problem

Testing Stripe integrations in Elixir (and in any language, really!) has some rough edges:

  • Stripe’s official stripe-mock is stateless - you can’t create a customer and then fetch it, which makes testing realistic flows impossible

  • Stripe’s webhook sandbox is limited - 5 endpoints max, dashboard-only management, no programmatic control

  • Shared test mode causes collisions - CI runs interfere with manual testing, leading to flaky tests

  • No time control - testing subscription renewals means waiting or hacking around Stripe’s billing cycle

We needed something that actually behaves like Stripe - stateful resources, proper webhook chains, and time control for billing cycles.

What PaperTiger Does

  • Stateful API - Create a customer, attach a payment method, subscribe them, and it all persists (in ETS)

  • Automatic webhook delivery - Resource changes emit properly-signed webhooks with retry logic

  • Time control - Accelerate time for subscription testing, or use manual mode for precise control

  • BillingEngine - Simulates subscription lifecycle: period rollovers, invoice creation, payment processing

  • Chaos mode - Inject payment failures with configurable decline codes for testing error paths

  • Contract testing - Run the same tests against PaperTiger and real Stripe to verify behavior matches

Quick Example

setup do
  {:ok, config: PaperTiger.stripity_stripe_config()}
end

test "subscription billing cycle", %{config: config} do
  {:ok, customer} = Stripe.Customer.create(%{email: "test@example.com"}, config)
  {:ok, sub} = Stripe.Subscription.create(%{customer: customer.id, ...}, config)
  
  assert sub.status == "active"
  
  # Advance time past the billing period
  PaperTiger.Clock.advance_days(32)
  
  # Subscription renewed, invoice created and paid
  {:ok, invoices} = Stripe.Invoice.list(%{customer: customer.id}, config)
  assert length(invoices.data) == 2
end

Links

https://github.com/EnaiaInc/paper_tiger

We’d welcome feedback, issues, or contributions. If you’ve dealt with similar Stripe testing headaches, hopefully this helps.

Most Liked

neilberkman

neilberkman

PaperTiger is now v1.0.0 and is much more usable than the early versions. For us internally, this has been a major win: we replaced a large amount of custom Stripe mocking code with PaperTiger-based tests, and it’s also been very useful in development mode. If you used it early on and had any difficulties you may want to try again as it should be significantly improved. Details in the changelog.

neilberkman

neilberkman

PaperTiger is now v1.2.1, and it has gotten a lot more useful for real billing-test suites since the 1.0.0 announcement.

PaperTiger is a stateful local Stripe mock for Elixir apps. It now covers enough of Stripe’s billing surface that you can test much more of your app without hitting Stripe test mode.

More real billing flows work locally

  • PaymentIntent confirm/cancel/capture, including Charges and BalanceTransactions.
  • SetupIntent confirm/cancel flows, Checkout Session updates/line items, and invoice send/mark-uncollectible/attach-payment flows.
  • Payment Links, Billing Portal sessions/configurations, Promotion Codes, Credit Notes, Customer Balance, and basic Connect platform flows.

The mock is stricter, and models Stripe more faithfully

  • Stripe-style search endpoints and much better list filtering.
  • More realistic SubscriptionSchedule behavior.
  • Idempotency keys now reject same-key/different-payload requests like Stripe does.

Testing hooks are nicer now

  • Pluggable webhook delivery, for apps that want to own durable webhook delivery/retry.
  • Signed webhook request helpers, so you can test the same verification code you use in production.
  • Request spy helpers, so tests can assert exactly what your app sent to PaperTiger.

Full changelog: Changelog — PaperTiger v1.2.1
Hex package: paper_tiger | Hex

Where Next?

Popular in Announcing Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
bryanjos
Hi, I just published version 0.23.0 of Elixirscript. https://github.com/bryanjos/elixirscript/blob/master/CHANGELOG.md Most of the chan...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
riverrun
I’ve just released version 3 of Comeonin, a password hashing library. The following small changes have been made: changes to the NIF c...
New
tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
Jskalc
Hi! Today, after a couple weeks of development I’ve released v0.1 of LiveVue. It’s a seamless integration of Vue and Phoenix LiveView, i...
New
Qqwy
TypeCheck: Fast and flexible runtime type-checking for your Elixir projects. Core ideas Type- and function specifications are const...
336 14327 100
New
marcuslankenau
I feel kind of stuck with the absence of a proper xml library for Elixir. Currently I use SweetXML which was ok for me more or less to pa...
New
kevinlang
Hey all, We have made an Ecto3 Adapter for SQLite3, ecto_sqlite3! We have successfully on-boarded the full suite of integration tests (...
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement