All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.0.2] - 2026-02-28
Added
- PaymentIntent confirm endpoint (
POST /v1/payment_intents/:id/confirm) with full Charge and BalanceTransaction creation on success. ChargeHelpermodule centralizing Charge + BalanceTransaction creation from succeeded PaymentIntents, used by checkout session completion, BillingEngine, and the new confirm endpoint.latest_chargefield on PaymentIntent objects, populated after successful payment.- Contract test validating full PaymentIntent -> Charge -> BalanceTransaction chain against real Stripe API.
Fixed
- Checkout session completion now creates Charge and BalanceTransaction (previously only created the PaymentIntent).
- Currency derivation from
line_items[].price_data.currencywhen not explicitly set on checkout session.
[1.0.1] - 2026-02-27
Fixed
- Metadata updates now use Stripe merge semantics: new keys are merged into existing metadata instead of replacing the entire map, and keys set to empty string are deleted.
Changed
- Nix/direnv developer setup is now opt-in (not auto-activated).
[1.0.0] - 2026-02-13
Added
- Invoice preview endpoints:
GET /v1/invoices/upcomingandPOST /v1/invoices/create_preview, including proration line generation and Stripe-style quantity validation. - Subscription payment-intent lifecycle coverage for
payment_behavior: "default_incomplete", including support forexpand=["latest_invoice.payment_intent"]. - Coupon/discount support in subscription create/update flows with discount reflection in invoice previews.
- Nix/direnv developer setup support (
flake.nix,flake.lock,.envrc) and corresponding README guidance.
Changed
phx.serverstartup detection now relies on Phoenix's:serve_endpointssignal instead of argv pattern matching.- Proration invoice creation is constrained to billable subscription changes, reducing spurious invoice generation.
- Chaos API override matching now supports endpoint-specific custom responses and wildcard-prefix path handling.
Fixed
customer.subscription.updatedwebhooks now includeprevious_attributes, matching Stripe behavior.- No-op subscription updates no longer emit redundant webhooks.
- Hydrator expansion now traverses list paths (for example
items.data.price.product) correctly. - Customer filtering by
emailand form-encoded card expiration field coercion better match Stripe-compatible behavior.
[0.9.25] - 2026-02-10
Added
PaperTiger.flush_all/0to flush data across all namespaces (legacy behavior).
Changed
PaperTiger.flush/0now flushes only the current namespace (safe forasync: truetest suites usingPaperTiger.Testsandboxing).- Updated dependencies:
quokka2.12.0,plug_cowboy2.8.0,ex_doc0.40.1.
Fixed
- ChaosCoordinator config/state is now correctly isolated per namespace, preventing intermittent test failures from cross-test chaos leakage.
- Reduced flaky test assertions in billing engine chaos tests.
[0.9.24] - 2026-02-02
Added
- Automatic checkout session completion: Checkout sessions now auto-complete asynchronously, simulating customer completion of the hosted checkout page. For setup mode sessions, when a customer adds a payment method to an incomplete subscription, the first invoice is automatically paid, matching Stripe's production behavior.
[0.9.23] - 2026-01-30
Fixed
- Random port with runtime config: Port selection is now deterministic even when called from
config/runtime.exsbefore PaperTiger starts. The newPaperTiger.Portresolver caches the selected port on first call, ensuring both config evaluation and server startup use the same port. This eliminates connection refused errors when usingstripity_stripe_config()without explicit port. Works with any HTTP client - not tied to stripity_stripe.
[0.9.22] - 2026-01-28
Changed
- Random high ports by default: PaperTiger now picks a random available port in the 59000-60000 range by default, eliminating port conflicts when running multiple instances (tests + dev server, parallel test suites). Port availability is checked before binding, with automatic retry if port is in use. Set explicit port via
PAPER_TIGER_PORTenv var orport:option if needed. NewPaperTiger.get_port/0function returns the actual port selected. - Fixed all Elixir 1.20 type checker warnings: removed 33 unused
require Loggerstatements and fixed typing violation in hydrator module. Paper Tiger now compiles cleanly with Elixir 1.20.0-rc.1 with zero type warnings.
[0.9.21] - 2026-01-19
Changed
- Reduced startup log noise by changing initialization messages from
Logger.infotoLogger.debugacross all Store modules, Application, Bootstrap, and core services. Startup messages are only visible at debug level while operational logs remain at appropriate levels.
[0.9.20] - 2026-01-11
Changed
- Checkout sessions now auto-complete transparently: Checkout session URLs now point to PaperTiger's own endpoint (
/checkout/:id/complete) instead of fake Stripe URLs. When visited, the session auto-completes and redirects tosuccess_url. This eliminates the need forpaper_tiger_enabled?checks in application code - checkout flows now work identically in dev/test and production.
[0.9.19] - 2026-01-10
Added
- Chaos testing cleanup function: Added
ChaosCoordinator.cleanup/0andChaosHelpers.cleanup_chaos/0that reset chaos configuration AND flush all Paper Tiger stores. Call after chaos testing to prevent test data from being synced to the host application's database.
[0.9.18] - 2026-01-08
No changes yet.
[0.9.17] - 2026-01-08
Added
- Bootstrap worker for async data loading: Refactored startup initialization into a dedicated
PaperTiger.Bootstrapworker that handles async data loading without blocking application startup - DataSource behaviour: New
PaperTiger.DataSourcebehaviour enables synchronization of initial billing data from external application databases - Payment method sync from database: StripityStripe adapter now loads existing payment methods and generates placeholders for missing customer
default_sourcetokens
Changed
- Trialing → active subscription transitions: Subscriptions updated with a past or
:nowtrial_endnow correctly transition fromtrialingtoactivestatus interval_countoptional in recurring prices: Matches Stripe API specs whereinterval_countdefaults to 1 if not provided
[0.9.16] - 2026-01-07
Changed
StripityStripe adapter now syncs from database instead of Stripe API: Completely rewrote
PaperTiger.Adapters.StripityStripeto query local database tables (billing_customers,billing_subscriptions,billing_products,billing_prices,billing_plans) instead of calling the real Stripe API. This properly mocks Stripe for dev/PR environments using stripity_stripe's local data.Configuration required: Add to your config:
config :paper_tiger, repo: MyApp.RepoNote: Auto-sync on startup is disabled when using database sync (repo isn't available at PaperTiger startup). You must manually trigger sync after your application starts, typically in your application's
start/2callback after the repo is started:# In your application.ex def start(_type, _args) do children = [MyApp.Repo, ...] opts = [strategy: :one_for_one, name: MyApp.Supervisor] result = Supervisor.start_link(children, opts) # Sync PaperTiger from database after repo is started if Application.get_env(:paper_tiger, :repo) do PaperTiger.Adapters.StripityStripe.sync_all() end result end
Added
- User adapter architecture: New
PaperTiger.UserAdapterbehavior allows customizing how user information (name, email) is retrieved for customers during sync - Auto-discovering user adapter:
PaperTiger.UserAdapters.AutoDiscoverautomatically discovers common user schema patterns including:- Email fields:
email,email_address, or foreign keyprimary_email_id→emails.address - Name fields:
name,full_name, orfirst_name + last_name - User tables:
usersoruser
- Email fields:
- Plan ID support for subscriptions: Subscription creation now accepts both
price_idandplan_id(legacy) for the:priceparameter, matching Stripe API behavior. Plans are automatically converted to price format in responses.
[0.9.15] - 2026-01-06
Added
- Stripe data sync adapter: Automatically syncs customer, subscription, product, price, and plan data from real Stripe API on startup when stripity_stripe is detected. Solves the problem of dev/PR apps losing subscription data on restart. Sync adapter is pluggable via
PaperTiger.SyncAdapterbehavior for custom implementations.
Changed
- Reduced debug logging: Removed per-operation debug logs from store operations (insert/update/delete/clear) and resource creation. Startup logging is now more concise with single-line summaries.
[0.9.14] - 2026-01-05
Fixed
init_datapriv paths now work in releases: Paths starting withpriv/(e.g.,init_data: "priv/paper_tiger/init_data.json") are now automatically resolved by searching all loaded applications' priv directories. This fixes init_data not loading in releases where the working directory differs from the project root.
[0.9.13] - 2026-01-04
Fixed
- Mix.env() check at runtime, not compile time: Dependencies are compiled in
:devenvironment by default, so the compile-time@mix_envmodule attribute was always:deveven when running tests. Now checksMix.env()at runtime (after verifying Mix is available) to correctly detect test environment.
[0.9.12] - 2026-01-04
Fixed
- Namespace isolation for InvoiceItem: Fixed
InvoiceItem.listto properly filter by namespace, preventing test isolation leaks when listing invoice items.
[0.9.11] - 2026-01-04
Fixed
- Mix.env() called at compile time: Fixed crash in releases where
Mix.env()was called at runtime but Mix isn't available in releases. Now captured at compile time via module attribute.
[0.9.10] - 2026-01-04
Added
PaperTiger.StripityStripeHackneyfor automatic sandbox isolation: New HTTP module that wraps:hackneyand injects namespace headers for test isolation when using stripity_stripe- Configure stripity_stripe with
http_module: PaperTiger.StripityStripeHackney - Works with child processes (LiveView, async tasks) via shared namespace in Application env
checkout_paper_tiger/1now automatically sets up shared namespace for child process support
- Configure stripity_stripe with
Changed
checkout_paper_tiger/1sets shared namespace via Application env: Child processes (like Phoenix LiveView) can now automatically access the same PaperTiger sandbox as the test process without additional configuration
[0.9.9] - 2026-01-04
Added
- Pre-defined Stripe test payment method tokens: PaperTiger now provides all standard Stripe test tokens (
pm_card_visa,pm_card_mastercard,pm_card_amex,tok_visa, etc.) out of the box- Card brand tokens: visa, mastercard, amex, discover, diners, jcb, unionpay (plus debit/prepaid variants)
- Decline test cards:
pm_card_chargeDeclined,pm_card_chargeDeclinedInsufficientFunds,pm_card_chargeDeclinedFraudulent, etc. - Tokens are loaded at startup and persist across
flush()calls - Test tokens work in namespace-isolated tests via global namespace fallback
[0.9.8] - 2026-01-03
Fixed
- Contract tests use pmcard* tokens: PaymentMethod contract tests now use Stripe test tokens (
pm_card_visa,pm_card_mastercard,pm_card_amex) instead of raw card data, which works with both PaperTiger mock and real Stripe API
[0.9.7] - 2026-01-03
Fixed
- Invoice
chargefield matches real Stripe behavior: Draft invoices no longer include thechargekey at all (not nil, just absent), matching real Stripe API behavior
Added
- Centralized test card helpers:
TestClient.test_card/0for real Stripe API testing andTestClient.test_card_simple/0for PaperTiger-style card data
[0.9.6] - 2026-01-03
Added
get_optional_integer/2helper: Distinguishes "key not present" from "0" for optional integer params liketrial_endnormalize_integer_map/1helper: Converts string integer values in maps (e.g., form-encoded params) to actual integers- Auto-create Plan for recurring Prices: When
Price.createis called withrecurringparams, a matching Plan object is automatically created (Stripe legacy API compatibility)
Fixed
- Subscription default status is "active": Fixed bug where subscriptions without trial periods incorrectly defaulted to "trialing" status
- Explicit status parameter respected:
Subscription.createnow respects explicitstatusparam instead of always computing it - Subscription list filtering: Uses
list_namespace/1for proper namespace-scoped queries instead of undefined store methods
[0.9.5] - 2026-01-03
Added
- Subscription items include
planfield for backwards compatibility: Stripe API populates bothplanandpriceon subscription items. PaperTiger now does the same viabuild_plan_from_price/1. - PaymentMethod.create supports custom IDs: Use
idparameter to create payment methods with deterministic IDs for testing. - Contract tests for subscription item plan field and payment method custom IDs
Fixed
- Price.recurring now includes
interval_count: Addedbuild_recurring/1function that defaultsinterval_countto 1 when not specified, matching Stripe API behavior. - Invoice list filtering by status: Fixed status filter to work correctly with string status values.
- PaymentMethod.list now requires customer parameter: Matches real Stripe API behavior. Returns empty list without customer param.
- PaymentMethods.find_by_customer uses proper namespacing: Fixed ETS query to use namespace-scoped keys for test isolation.
[0.9.4] - 2026-01-02
Added
ChaosCoordinator for unified chaos testing: New module consolidating all chaos testing capabilities
- Payment chaos: configurable failure rates, decline codes, per-customer overrides
- Event chaos: out-of-order delivery, duplicate events, buffered delivery windows
- API chaos: timeout simulation, rate limiting, server errors
- Statistics tracking for all chaos types
- Integrated with
Invoice.payfor realistic payment failure simulation
Contract tests for InvoiceItem, Invoice finalize/pay, and card decline errors
Fixed
- Subscription status now matches Stripe API exactly (e.g.,
activevstrialing) - TestClient normalizes delete responses with
deleted=truefield - Card decline test assertions check correct fields
Changed
- Clock uses ETS for lock-free reads:
now/0reads directly from ETS instead of GenServer call, avoiding bottleneck under load - Hydrator uses compile-time prefix registry: No runtime map traversal for ID prefix lookups
- Idempotency uses atomic select_delete: Fixes potential race condition
- ChaosCoordinator uses namespace isolation: Per-namespace ETS state with proper timer cancellation on reset
- Store modules export
prefixoption for Hydrator registry - Tests use
assert_receiveinstead ofProcess.sleepfor reliability
[0.9.3] - 2026-01-02
Added
- Test sandbox for concurrent test support: New
PaperTiger.Testmodule provides Ecto SQL Sandbox-style test isolation- Use
setup :checkout_paper_tigerto isolate test data per process - Tests can now run with
async: truewithout data interference - All stores now support namespace-scoped operations
- Automatic cleanup on test exit
- Use
- HTTP sandbox via headers:
PaperTiger.Plugs.Sandboxenables sandbox isolation for HTTP API tests- Include
x-paper-tiger-namespaceheader to scope HTTP requests to a test namespace - New
PaperTiger.Test.sandbox_headers/0returns headers for sandbox isolation - New
PaperTiger.Test.auth_headers/1combines auth + sandbox headers - New
PaperTiger.Test.base_url/1helper for building PaperTiger URLs
- Include
Changed
- Storage layer uses namespaced keys: All ETS stores now key data by
{namespace, id}instead of justid- Backwards compatible: non-sandboxed code uses
:globalnamespace automatically - New functions:
clear_namespace/1,list_namespace/1on all stores - Idempotency cache also supports namespacing
- Backwards compatible: non-sandboxed code uses
[0.9.2] - 2026-01-02
Fixed
- Proper Stripe error responses for missing resources: Instead of crashing, PaperTiger now returns the same error format as Stripe when a resource doesn't exist
- Returns
resource_missingerror code with proper message format: "No such <resource>: '<id>'" - Includes correct
paramvalues matching Stripe (e.g.,idfor customers,pricefor prices) - HTTP 404 status code for not found errors
- Returns
Added
- Contract tests verifying error responses match Stripe's format
[0.9.1] - 2026-01-02
Fixed
- Events missing
delivery_attemptsfield: Events created via telemetry now includedelivery_attempts: []field, fixing KeyError when accessing this field
Added
- Auto-register webhooks from application config on startup: PaperTiger now automatically registers webhooks configured via
config :paper_tiger, webhooks: [...]when the application starts, eliminating need for manual registration in your Application module
[0.9.0] - 2026-01-02
Fixed
- Subscription
latest_invoice: Now populated with the actual latest Invoice object for the subscription instead of always being null - PaymentIntent
chargesfield removed: Real Stripe API does not includechargeson PaymentIntent - charges are accessed via separate endpointGET /v1/charges?payment_intent=pi_xxx. PaperTiger now matches this behavior. - Charge
balance_transaction: Successful charges now create and link a BalanceTransaction with proper fee calculation (2.9% + $0.30) - Refund
balance_transaction: Refunds now create and link a BalanceTransaction with negative amounts - Contract tests now run against real Stripe: Removed all
paper_tiger_onlytagged tests. All contract tests now pass against both PaperTiger mock and real Stripe API.
Added
- Checkout Session completion support: New endpoints for completing and expiring checkout sessions
POST /v1/checkout/sessions/:id/expire- Expires an open session (matches Stripe API)POST /_test/checkout/sessions/:id/complete- Test helper to simulate successful checkout completion- Based on mode, creates appropriate side effects:
payment: Creates a succeeded PaymentIntentsubscription: Creates an active Subscription with itemssetup: Creates a succeeded SetupIntent
- Fires
checkout.session.completedandcheckout.session.expiredwebhook events - Creates PaymentMethod and fires
payment_method.attachedevent on completion
- Environment-specific port configuration: New env vars
PAPER_TIGER_PORT_DEVandPAPER_TIGER_PORT_TESTallow different ports per Mix environment. Enables running dev server and tests simultaneously without port conflicts. Precedence:PAPER_TIGER_PORT_{ENV}>PAPER_TIGER_PORT> config > 4001. PaperTiger.BalanceTransactionHelpermodule for creating balance transactions with Stripe-compatible fee calculations
Removed
- PaymentMethod raw card number support tests: Tests using raw card numbers don't work with real Stripe API. Use test tokens like
pm_card_visainstead.
[0.8.5] - 2026-01-02
Added
- Synchronous webhook delivery mode: Configure
webhook_mode: :syncto have API calls block until webhooks are delivered. Useful for testing where you need to assert on webhook side effects immediately after API calls. WebhookDelivery.deliver_event_sync/2function for explicit synchronous delivery
[0.8.4] - 2026-01-02
Fixed
- Subscription items now return full price object:
subscription.items.data[].priceis now a full price object (withid,object,currency, etc.) instead of just the price ID string, matching real Stripe API behavior - Same fix applied to
subscription_item.pricewhen creating/updating subscription items directly - When price doesn't exist in the store, returns a minimal price object with required fields for API compatibility
Added
- Contract test validating subscription item price structure against real Stripe API
TestClient.create_product/1andTestClient.create_price/1helpers for contract testing
[0.8.3] - 2026-01-02
Fixed
- Remove unused
cleanup_payment_method/1function that caused warnings-as-errors CI failure
[0.8.2] - 2026-01-02
Fixed
- Contract test fixes: Tests now properly pass the API key to all stripity_stripe calls
- PaymentMethod and Subscription tests: Skipped for real Stripe (require tokens/pre-created prices that can't be created via API) - these test PaperTiger's convenience features
- Clearer test mode messaging: Contract tests now display "RUNNING AGAINST REAL STRIPE TEST API" with explicit "API key validated as TEST MODE"
[0.8.1] - 2026-01-02
Added
Live key safety guard:
TestClientnow performs two-layer validation before running contract tests against real Stripe:- Validates API key prefix (rejects
sk_live_*,rk_live_*) - Makes a live API call to
/v1/balanceand verifieslivemode: false
This prevents accidental production usage even if someone crafts a key with a fake prefix
- Validates API key prefix (rejects
Fixed
- BillingEngine now retries existing open invoices instead of creating duplicates on each billing cycle
- Subscriptions correctly marked
past_dueafter 4 failed payment attempts
[0.8.0] - 2026-01-01
Added
PaperTiger.BillingEngineGenServer for subscription billing lifecycle simulation- Processes subscriptions whose
current_period_endhas passed - Creates invoices, payment intents, and charges automatically
- Fires all relevant telemetry events for webhook delivery (invoice.created, charge.succeeded, etc.)
- Two billing modes:
:happy_path(all payments succeed) and:chaos(random failures) - Per-customer failure simulation via
BillingEngine.simulate_failure/2 - Configurable chaos mode with custom failure rates and decline codes
- Integrates with PaperTiger's clock modes (real, accelerated, manual)
invoice.upcomingtelemetry event support in TelemetryHandler- Enable with config:
config :paper_tiger, :billing_engine, true
[0.7.1] - 2026-01-01
Added
- Custom ID support for deterministic data - pass
idparameter to create endpoints for Customer, Subscription, Invoice, Product, and Price resources - Enables stable
stripe_idvalues across database resets for testing scenarios PaperTiger.Initializermodule for loading initial data from config on startup- Config option
init_dataaccepts JSON file path or inline map with products, prices, and customers - Initial data loads automatically after ETS stores initialize, ensuring data is available before dependent apps start
[0.7.0] - 2026-01-01
Added
- Automatic event emission via telemetry - resource operations (create/update/delete) now automatically emit Stripe events and deliver webhooks
PaperTiger.TelemetryHandlermodule for bridging resource operations to webhook delivery- Comprehensive Stripe API coverage including Customers, Subscriptions, Invoices, PaymentMethods, Products, Prices, and more
- ETS-backed storage layer with concurrent reads and serialized writes
- HMAC-signed webhook delivery with exponential backoff retry logic
- Dual-mode contract testing (PaperTiger vs real Stripe API)
- Time control (real, accelerated, manual modes)
- Idempotency key support with 24-hour TTL
- Object expansion (hydrator system for nested resources)
PaperTiger.stripity_stripe_config/1helper for easy stripity_stripe integrationPaperTiger.register_configured_webhooks/0for automatic webhook registration from config- Environment variable support:
PAPER_TIGER_AUTO_STARTandPAPER_TIGER_PORT - Phoenix integration helpers and documentation
- Interactive Livebook tutorial (
examples/getting_started.livemd)