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.
  • ChargeHelper module centralizing Charge + BalanceTransaction creation from succeeded PaymentIntents, used by checkout session completion, BillingEngine, and the new confirm endpoint.
  • latest_charge field 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.currency when 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/upcoming and POST /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 for expand=["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.server startup detection now relies on Phoenix's :serve_endpoints signal 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.updated webhooks now include previous_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 email and form-encoded card expiration field coercion better match Stripe-compatible behavior.

[0.9.25] - 2026-02-10

Added

Changed

  • PaperTiger.flush/0 now flushes only the current namespace (safe for async: true test suites using PaperTiger.Test sandboxing).
  • Updated dependencies: quokka 2.12.0, plug_cowboy 2.8.0, ex_doc 0.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.exs before PaperTiger starts. The new PaperTiger.Port resolver caches the selected port on first call, ensuring both config evaluation and server startup use the same port. This eliminates connection refused errors when using stripity_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_PORT env var or port: option if needed. New PaperTiger.get_port/0 function returns the actual port selected.
  • Fixed all Elixir 1.20 type checker warnings: removed 33 unused require Logger statements 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.info to Logger.debug across 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 to success_url. This eliminates the need for paper_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/0 and ChaosHelpers.cleanup_chaos/0 that 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.Bootstrap worker that handles async data loading without blocking application startup
  • DataSource behaviour: New PaperTiger.DataSource behaviour 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_source tokens

Changed

  • Trialing → active subscription transitions: Subscriptions updated with a past or :now trial_end now correctly transition from trialing to active status
  • interval_count optional in recurring prices: Matches Stripe API specs where interval_count defaults 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.StripityStripe to 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.Repo

    Note: 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/2 callback 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.UserAdapter behavior allows customizing how user information (name, email) is retrieved for customers during sync
  • Auto-discovering user adapter: PaperTiger.UserAdapters.AutoDiscover automatically discovers common user schema patterns including:
    • Email fields: email, email_address, or foreign key primary_email_idemails.address
    • Name fields: name, full_name, or first_name + last_name
    • User tables: users or user
  • Plan ID support for subscriptions: Subscription creation now accepts both price_id and plan_id (legacy) for the :price parameter, 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.SyncAdapter behavior 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_data priv paths now work in releases: Paths starting with priv/ (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 :dev environment by default, so the compile-time @mix_env module attribute was always :dev even when running tests. Now checks Mix.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.list to 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.StripityStripeHackney for automatic sandbox isolation: New HTTP module that wraps :hackney and 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/1 now automatically sets up shared namespace for child process support

Changed

  • checkout_paper_tiger/1 sets 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 charge field matches real Stripe behavior: Draft invoices no longer include the charge key at all (not nil, just absent), matching real Stripe API behavior

Added

  • Centralized test card helpers: TestClient.test_card/0 for real Stripe API testing and TestClient.test_card_simple/0 for PaperTiger-style card data

[0.9.6] - 2026-01-03

Added

  • get_optional_integer/2 helper: Distinguishes "key not present" from "0" for optional integer params like trial_end
  • normalize_integer_map/1 helper: Converts string integer values in maps (e.g., form-encoded params) to actual integers
  • Auto-create Plan for recurring Prices: When Price.create is called with recurring params, 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.create now respects explicit status param instead of always computing it
  • Subscription list filtering: Uses list_namespace/1 for proper namespace-scoped queries instead of undefined store methods

[0.9.5] - 2026-01-03

Added

  • Subscription items include plan field for backwards compatibility: Stripe API populates both plan and price on subscription items. PaperTiger now does the same via build_plan_from_price/1.
  • PaymentMethod.create supports custom IDs: Use id parameter 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: Added build_recurring/1 function that defaults interval_count to 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.pay for 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., active vs trialing)
  • TestClient normalizes delete responses with deleted=true field
  • Card decline test assertions check correct fields

Changed

  • Clock uses ETS for lock-free reads: now/0 reads 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 prefix option for Hydrator registry
  • Tests use assert_receive instead of Process.sleep for reliability

[0.9.3] - 2026-01-02

Added

  • Test sandbox for concurrent test support: New PaperTiger.Test module provides Ecto SQL Sandbox-style test isolation
    • Use setup :checkout_paper_tiger to isolate test data per process
    • Tests can now run with async: true without data interference
    • All stores now support namespace-scoped operations
    • Automatic cleanup on test exit
  • HTTP sandbox via headers: PaperTiger.Plugs.Sandbox enables sandbox isolation for HTTP API tests

Changed

  • Storage layer uses namespaced keys: All ETS stores now key data by {namespace, id} instead of just id
    • Backwards compatible: non-sandboxed code uses :global namespace automatically
    • New functions: clear_namespace/1, list_namespace/1 on all stores
    • Idempotency cache also supports namespacing

[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_missing error code with proper message format: "No such <resource>: '<id>'"
    • Includes correct param values matching Stripe (e.g., id for customers, price for prices)
    • HTTP 404 status code for not found errors

Added

  • Contract tests verifying error responses match Stripe's format

[0.9.1] - 2026-01-02

Fixed

  • Events missing delivery_attempts field: Events created via telemetry now include delivery_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 charges field removed: Real Stripe API does not include charges on PaymentIntent - charges are accessed via separate endpoint GET /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_only tagged 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 PaymentIntent
      • subscription: Creates an active Subscription with items
      • setup: Creates a succeeded SetupIntent
    • Fires checkout.session.completed and checkout.session.expired webhook events
    • Creates PaymentMethod and fires payment_method.attached event on completion
  • Environment-specific port configuration: New env vars PAPER_TIGER_PORT_DEV and PAPER_TIGER_PORT_TEST allow 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.BalanceTransactionHelper module 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_visa instead.

[0.8.5] - 2026-01-02

Added

  • Synchronous webhook delivery mode: Configure webhook_mode: :sync to 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/2 function for explicit synchronous delivery

[0.8.4] - 2026-01-02

Fixed

  • Subscription items now return full price object: subscription.items.data[].price is now a full price object (with id, object, currency, etc.) instead of just the price ID string, matching real Stripe API behavior
  • Same fix applied to subscription_item.price when 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/1 and TestClient.create_price/1 helpers for contract testing

[0.8.3] - 2026-01-02

Fixed

  • Remove unused cleanup_payment_method/1 function 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: TestClient now performs two-layer validation before running contract tests against real Stripe:

    1. Validates API key prefix (rejects sk_live_*, rk_live_*)
    2. Makes a live API call to /v1/balance and verifies livemode: false

    This prevents accidental production usage even if someone crafts a key with a fake prefix

Fixed

  • BillingEngine now retries existing open invoices instead of creating duplicates on each billing cycle
  • Subscriptions correctly marked past_due after 4 failed payment attempts

[0.8.0] - 2026-01-01

Added

  • PaperTiger.BillingEngine GenServer for subscription billing lifecycle simulation
  • Processes subscriptions whose current_period_end has 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.upcoming telemetry 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 id parameter to create endpoints for Customer, Subscription, Invoice, Product, and Price resources
  • Enables stable stripe_id values across database resets for testing scenarios
  • PaperTiger.Initializer module for loading initial data from config on startup
  • Config option init_data accepts 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.TelemetryHandler module 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/1 helper for easy stripity_stripe integration
  • PaperTiger.register_configured_webhooks/0 for automatic webhook registration from config
  • Environment variable support: PAPER_TIGER_AUTO_START and PAPER_TIGER_PORT
  • Phoenix integration helpers and documentation
  • Interactive Livebook tutorial (examples/getting_started.livemd)