PaperTiger.BillingEngine (PaperTiger v1.5.1)

Copy Markdown View Source

Simulates Stripe's subscription billing lifecycle.

The billing engine processes subscriptions whose current_period_end has passed, creating invoices, processing payments, and firing appropriate webhooks.

Billing Modes

Payment chaos is now managed by PaperTiger.ChaosCoordinator. Use:

PaperTiger.ChaosCoordinator.configure(%{
  payment: %{
    failure_rate: 0.1,
    decline_codes: [:card_declined, :insufficient_funds],
    decline_weights: %{card_declined: 0.7, insufficient_funds: 0.3}
  }
})

Legacy set_mode/2 API is still supported for backwards compatibility.

Clock Integration

The billing engine respects PaperTiger's clock modes:

  • :real - Polls every second for due subscriptions
  • :accelerated - Same polling, but time moves faster
  • :manual - Call process_billing/0 after advance_time/1

Usage

# Process all due subscriptions immediately
PaperTiger.BillingEngine.process_billing()

# Simulate payment failure for a specific customer (delegates to ChaosCoordinator)
PaperTiger.ChaosCoordinator.simulate_failure("cus_xxx", :card_declined)

# Clear failure simulation
PaperTiger.ChaosCoordinator.clear_simulation("cus_xxx")

Summary

Functions

Returns a specification to start this module under a supervisor.

Clears any payment simulation for a customer (delegates to ChaosCoordinator).

Gets the current billing mode (legacy API).

Processes the subscriptions that are due for billing.

Sets the billing mode (legacy API, delegates to ChaosCoordinator).

Simulates a payment failure for a specific customer (delegates to ChaosCoordinator).

Starts the billing engine.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear_simulation(customer_id)

@spec clear_simulation(String.t()) :: :ok

Clears any payment simulation for a customer (delegates to ChaosCoordinator).

get_mode()

@spec get_mode() :: atom()

Gets the current billing mode (legacy API).

Returns :chaos if payment failure rate > 0, otherwise :happy_path.

process_billing(scope \\ :current)

@spec process_billing(:current | :all) :: {:ok, map()}

Processes the subscriptions that are due for billing.

With :current (the default) only the caller's storage namespace is processed: its sandbox and, when set, its connected account. That is the right scope after advancing a manual clock in a test. With :all every namespace that holds subscriptions is processed, which is what the automatic poll does.

set_mode(mode, opts \\ [])

@spec set_mode(
  atom(),
  keyword()
) :: :ok

Sets the billing mode (legacy API, delegates to ChaosCoordinator).

Modes

  • :happy_path - All payments succeed
  • :chaos - Random failures based on configured rates

Options (for chaos mode)

  • :payment_failure_rate - Probability of payment failure (0.0 - 1.0)
  • :decline_codes - List of decline codes to use randomly
  • :decline_code_weights - Map of decline code to weight for realistic distribution

Prefer using PaperTiger.ChaosCoordinator.configure/1 directly for new code.

simulate_failure(customer_id, decline_code)

@spec simulate_failure(String.t(), atom()) :: :ok

Simulates a payment failure for a specific customer (delegates to ChaosCoordinator).

start_link(opts \\ [])

Starts the billing engine.