Packages

phoenix_kit

1.7.78
1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules billing web invoice_detail helpers.ex
Raw

lib/modules/billing/web/invoice_detail/helpers.ex

defmodule PhoenixKit.Modules.Billing.Web.InvoiceDetail.Helpers do
@moduledoc """
Helper functions for the invoice detail LiveView.
Contains timeline building, history parsing, formatting,
and other template-callable utilities.
"""
alias PhoenixKit.Modules.Billing.Web.InvoiceDetail.TimelineEvent
@doc """
Gets the default email address from invoice billing details or user.
"""
def get_default_email(invoice) do
cond do
invoice.billing_details["email"] -> invoice.billing_details["email"]
invoice.user -> invoice.user.email
true -> ""
end
end
@doc """
Gets send history from invoice metadata.
"""
def get_send_history(invoice) do
case invoice.metadata do
%{"send_history" => history} when is_list(history) -> history
_ -> []
end
end
@doc """
Gets receipt send history from invoice receipt_data.
"""
def get_receipt_send_history(invoice) do
case invoice.receipt_data do
%{"send_history" => history} when is_list(history) -> history
_ -> []
end
end
@doc """
Gets credit note send history from transaction metadata.
"""
def get_credit_note_send_history(transaction) do
case transaction.metadata do
%{"credit_note_send_history" => history} when is_list(history) -> history
_ -> []
end
end
@doc """
Parses ISO8601 datetime string to DateTime.
"""
def parse_datetime(nil), do: nil
def parse_datetime(datetime_string) when is_binary(datetime_string) do
case DateTime.from_iso8601(datetime_string) do
{:ok, datetime, _offset} -> datetime
_ -> nil
end
end
def parse_datetime(datetime), do: datetime
@doc """
Builds a sorted timeline of all invoice events.
Returns a list of `%TimelineEvent{}` structs sorted by datetime.
"""
def build_timeline_events(invoice, transactions) do
events = []
# 1. Created event
events = [%TimelineEvent{type: :created, datetime: invoice.inserted_at} | events]
# 2. Invoice sent events
invoice_sends =
get_send_history(invoice)
|> Enum.map(fn entry ->
%TimelineEvent{
type: :invoice_sent,
datetime: parse_datetime(entry["sent_at"]),
data: entry
}
end)
events = events ++ invoice_sends
# Fallback for old invoices without send_history
events =
if invoice.sent_at && Enum.empty?(get_send_history(invoice)) do
[%TimelineEvent{type: :invoice_sent_legacy, datetime: invoice.sent_at} | events]
else
events
end
# 3. Payment transactions (positive amounts)
payment_events =
transactions
|> Enum.filter(&Decimal.positive?(&1.amount))
|> Enum.map(fn txn ->
%TimelineEvent{type: :payment, datetime: txn.inserted_at, data: txn}
end)
events = events ++ payment_events
# 4. Paid event (when fully paid)
events =
if invoice.paid_at do
[%TimelineEvent{type: :paid, datetime: invoice.paid_at} | events]
else
events
end
# 5. Receipt generated
events =
if invoice.receipt_number do
[
%TimelineEvent{
type: :receipt_generated,
datetime: invoice.receipt_generated_at,
data: invoice.receipt_number
}
| events
]
else
events
end
# 6. Receipt sent events
receipt_sends =
get_receipt_send_history(invoice)
|> Enum.map(fn entry ->
%TimelineEvent{
type: :receipt_sent,
datetime: parse_datetime(entry["sent_at"]),
data: entry
}
end)
events = events ++ receipt_sends
# 7. Refund transactions and their credit note sends
refund_events =
transactions
|> Enum.filter(&Decimal.negative?(&1.amount))
|> Enum.flat_map(fn txn ->
# Refund event itself
refund_event = %TimelineEvent{type: :refund, datetime: txn.inserted_at, data: txn}
# Credit note send events for this refund
credit_note_sends =
get_credit_note_send_history(txn)
|> Enum.map(fn entry ->
%TimelineEvent{
type: :credit_note_sent,
datetime: parse_datetime(entry["sent_at"]),
data: Map.put(entry, "transaction", txn)
}
end)
[refund_event | credit_note_sends]
end)
events = events ++ refund_events
# 8. Voided event
events =
if invoice.voided_at do
[%TimelineEvent{type: :voided, datetime: invoice.voided_at} | events]
else
events
end
# Sort by datetime (nil datetimes go to the end)
events
|> Enum.sort_by(
fn event ->
case event.datetime do
nil -> {1, 0}
dt -> {0, DateTime.to_unix(dt, :microsecond)}
end
end,
:asc
)
end
@doc """
Checks if invoice is fully refunded.
"""
def fully_refunded?(invoice, transactions) do
total_refunded =
transactions
|> Enum.filter(&Decimal.negative?(&1.amount))
|> Enum.map(& &1.amount)
|> Enum.reduce(Decimal.new(0), &Decimal.add/2)
|> Decimal.abs()
Decimal.gt?(total_refunded, Decimal.new(0)) &&
Decimal.gte?(total_refunded, invoice.total)
end
@doc """
Formats payment method name for display.
"""
def format_payment_method_name("bank"), do: "Bank Transfer"
def format_payment_method_name("stripe"), do: "Stripe"
def format_payment_method_name("paypal"), do: "PayPal"
def format_payment_method_name("razorpay"), do: "Razorpay"
def format_payment_method_name(other) when is_binary(other), do: String.capitalize(other)
def format_payment_method_name(_), do: "Unknown"
end