PaperTiger.Proration (PaperTiger v1.5.0)

Copy Markdown View Source

Proration math shared by the immediate proration invoice (subscription updates) and the proration lines in its preview (POST /v1/invoices/create_preview).

One module on purpose: the previewed proration lines quote the immediate adjustment, so both paths must use the same remaining-period ratio, item diff, and currency.

Amounts follow Stripe's model: credit the unused remainder of what was removed, charge the remainder of what was added, both scaled by how much of the current billing period is left.

Summary

Functions

Invoice-level currency for a list of built lines.

Proration line items for a change from old_items to new_items, scaled by ratio. Items are %{price_id, product, quantity, unit_amount} maps; quantities are aggregated per price, and a price whose total amount did not change produces no lines. Line currency comes from the price in the store, falling back to "usd" for a price it does not hold.

The currency of a price in the store, falling back to "usd" for one it does not hold. Also the invoice-level currency for a set of lines: the first line's currency, on the Stripe invariant that one invoice carries one currency.

The share of the current billing period still ahead at now, clamped to 0..1. A subscription without a sane period (missing fields, zero-length period) prorates at 1.0 — full charge, full credit — which is at least predictable where the period math would be garbage.

Functions

invoice_currency(arg1)

@spec invoice_currency([map()]) :: String.t()

Invoice-level currency for a list of built lines.

lines(old_items, new_items, ratio, now)

@spec lines([map()], [map()], float(), integer()) :: [map()]

Proration line items for a change from old_items to new_items, scaled by ratio. Items are %{price_id, product, quantity, unit_amount} maps; quantities are aggregated per price, and a price whose total amount did not change produces no lines. Line currency comes from the price in the store, falling back to "usd" for a price it does not hold.

price_currency(price_id)

@spec price_currency(String.t() | nil) :: String.t()

The currency of a price in the store, falling back to "usd" for one it does not hold. Also the invoice-level currency for a set of lines: the first line's currency, on the Stripe invariant that one invoice carries one currency.

remaining_ratio(subscription, now)

@spec remaining_ratio(map(), integer()) :: float()

The share of the current billing period still ahead at now, clamped to 0..1. A subscription without a sane period (missing fields, zero-length period) prorates at 1.0 — full charge, full credit — which is at least predictable where the period math would be garbage.