# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [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
- `PaperTiger.flush_all/0` to flush data across all namespaces (legacy behavior).
### 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:
```elixir
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:
```elixir
# 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_id` → `emails.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 pm*card*\* 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
- Include `x-paper-tiger-namespace` header to scope HTTP requests to a test namespace
- New `PaperTiger.Test.sandbox_headers/0` returns headers for sandbox isolation
- New `PaperTiger.Test.auth_headers/1` combines auth + sandbox headers
- New `PaperTiger.Test.base_url/1` helper for building PaperTiger URLs
### 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`)