Packages
phoenix_kit
1.7.78
1.7.211
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
Current section
Files
lib/modules/billing/web/invoice_detail/actions.ex
defmodule PhoenixKit.Modules.Billing.Web.InvoiceDetail.Actions do
@moduledoc """
Action handlers for the invoice detail LiveView.
Contains business logic for payment recording, refunds,
sending documents, voiding, and receipt generation.
Each function takes a socket and returns `{:noreply, socket}`.
"""
import Phoenix.LiveView, only: [put_flash: 3, redirect: 2]
alias PhoenixKit.Modules.Billing
alias PhoenixKit.Utils.Routes
def record_payment(socket) do
%{
invoice: invoice,
payment_amount: amount,
payment_description: desc,
selected_payment_method: payment_method
} = socket.assigns
current_scope = socket.assigns[:phoenix_kit_current_scope]
attrs = %{
amount: amount,
payment_method: payment_method,
description: if(desc == "", do: nil, else: desc)
}
case Billing.record_payment(invoice, attrs, current_scope) do
{:ok, _transaction} ->
socket = reload_invoice(socket)
{:noreply,
socket
|> Phoenix.Component.assign(:show_payment_modal, false)
|> put_flash(:info, "Payment recorded successfully")}
{:error, :not_payable} ->
{:noreply, put_flash(socket, :error, "Invoice cannot receive payments in current status")}
{:error, :exceeds_remaining} ->
{:noreply, put_flash(socket, :error, "Payment amount exceeds remaining balance")}
{:error, :invalid_amount} ->
{:noreply, put_flash(socket, :error, "Invalid payment amount")}
{:error, changeset} when is_struct(changeset, Ecto.Changeset) ->
{:noreply, put_flash(socket, :error, "Failed to record payment")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to record payment: #{inspect(reason)}")}
end
end
def pay_with_provider(socket, provider_str) do
provider = String.to_existing_atom(provider_str)
invoice = socket.assigns.invoice
success_url = Routes.url("/admin/billing/invoices/#{invoice.uuid}?payment=success")
cancel_url = Routes.url("/admin/billing/invoices/#{invoice.uuid}?payment=cancelled")
opts = [
success_url: success_url,
cancel_url: cancel_url,
currency: invoice.currency,
metadata: %{
invoice_uuid: invoice.uuid,
invoice_number: invoice.invoice_number
}
]
socket = Phoenix.Component.assign(socket, :checkout_loading, provider)
case Billing.create_checkout_session(invoice, provider, opts) do
{:ok, checkout_url} when is_binary(checkout_url) ->
{:noreply, redirect(socket, external: checkout_url)}
{:error, :provider_not_available} ->
{:noreply,
socket
|> Phoenix.Component.assign(:checkout_loading, nil)
|> put_flash(:error, "Payment provider #{provider} is not available")}
{:error, reason} ->
{:noreply,
socket
|> Phoenix.Component.assign(:checkout_loading, nil)
|> put_flash(:error, "Failed to create checkout session: #{inspect(reason)}")}
end
end
def record_refund(socket) do
%{
invoice: invoice,
refund_amount: amount,
refund_description: desc,
selected_refund_payment_method: payment_method
} = socket.assigns
current_scope = socket.assigns[:phoenix_kit_current_scope]
if desc == "" do
{:noreply, put_flash(socket, :error, "Refund reason is required")}
else
attrs = %{
amount: amount,
payment_method: payment_method,
description: desc
}
case Billing.record_refund(invoice, attrs, current_scope) do
{:ok, _transaction} ->
socket = reload_invoice(socket)
{:noreply,
socket
|> Phoenix.Component.assign(:show_refund_modal, false)
|> put_flash(:info, "Refund recorded successfully")}
{:error, :not_refundable} ->
{:noreply, put_flash(socket, :error, "Invoice has no payments to refund")}
{:error, :exceeds_paid_amount} ->
{:noreply, put_flash(socket, :error, "Refund amount exceeds paid amount")}
{:error, :invalid_amount} ->
{:noreply, put_flash(socket, :error, "Invalid refund amount")}
{:error, _reason} ->
{:noreply, put_flash(socket, :error, "Failed to record refund")}
end
end
end
def send_invoice(socket) do
invoice = socket.assigns.invoice
email = socket.assigns.send_email
invoice_url = Routes.url("/admin/billing/invoices/#{invoice.uuid}/print")
case Billing.send_invoice(invoice, invoice_url: invoice_url, to_email: email) do
{:ok, updated_invoice} ->
{:noreply,
socket
|> Phoenix.Component.assign(:invoice, updated_invoice)
|> Phoenix.Component.assign(:show_send_modal, false)
|> put_flash(:info, "Invoice sent to #{email}")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to send invoice: #{reason}")}
end
end
def send_receipt(socket) do
invoice = socket.assigns.invoice
email = socket.assigns.send_receipt_email
receipt_url = Routes.url("/admin/billing/invoices/#{invoice.uuid}/receipt")
case Billing.send_receipt(invoice, receipt_url: receipt_url, to_email: email) do
{:ok, updated_invoice} ->
{:noreply,
socket
|> Phoenix.Component.assign(:invoice, updated_invoice)
|> Phoenix.Component.assign(:show_send_receipt_modal, false)
|> put_flash(:info, "Receipt sent to #{email}")}
{:error, :invoice_not_paid} ->
{:noreply, put_flash(socket, :error, "Invoice must be paid before sending receipt")}
{:error, :receipt_not_generated} ->
{:noreply, put_flash(socket, :error, "Receipt has not been generated yet")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to send receipt: #{inspect(reason)}")}
end
end
def send_credit_note(socket) do
invoice = socket.assigns.invoice
email = socket.assigns.send_credit_note_email
transaction_uuid = socket.assigns.send_credit_note_transaction_uuid
transaction = Enum.find(socket.assigns.transactions, &(&1.uuid == transaction_uuid))
credit_note_url =
Routes.url("/admin/billing/invoices/#{invoice.uuid}/credit-note/#{transaction_uuid}")
with %{} <- transaction,
{:ok, updated_transaction} <-
Billing.send_credit_note(invoice, transaction,
credit_note_url: credit_note_url,
to_email: email
) do
updated_transactions =
update_transaction_in_list(socket.assigns.transactions, updated_transaction)
{:noreply,
socket
|> Phoenix.Component.assign(:transactions, updated_transactions)
|> Phoenix.Component.assign(:show_send_credit_note_modal, false)
|> Phoenix.Component.assign(:send_credit_note_transaction_uuid, nil)
|> put_flash(:info, "Credit note sent to #{email}")}
else
nil ->
{:noreply, put_flash(socket, :error, "Transaction not found")}
{:error, :not_a_refund} ->
{:noreply, put_flash(socket, :error, "Transaction is not a refund")}
{:error, :no_recipient_email} ->
{:noreply, put_flash(socket, :error, "No recipient email address")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to send credit note: #{inspect(reason)}")}
end
end
def send_payment_confirmation(socket) do
invoice = socket.assigns.invoice
email = socket.assigns.send_payment_confirmation_email
transaction_uuid = socket.assigns.send_payment_confirmation_transaction_uuid
transaction = Enum.find(socket.assigns.transactions, &(&1.uuid == transaction_uuid))
payment_url =
Routes.url("/admin/billing/invoices/#{invoice.uuid}/payment/#{transaction_uuid}")
with %{} <- transaction,
{:ok, updated_transaction} <-
Billing.send_payment_confirmation(invoice, transaction,
payment_url: payment_url,
to_email: email
) do
updated_transactions =
update_transaction_in_list(socket.assigns.transactions, updated_transaction)
{:noreply,
socket
|> Phoenix.Component.assign(:transactions, updated_transactions)
|> Phoenix.Component.assign(:show_send_payment_confirmation_modal, false)
|> Phoenix.Component.assign(:send_payment_confirmation_transaction_uuid, nil)
|> put_flash(:info, "Payment confirmation sent to #{email}")}
else
nil ->
{:noreply, put_flash(socket, :error, "Transaction not found")}
{:error, :not_a_payment} ->
{:noreply, put_flash(socket, :error, "Transaction is not a payment")}
{:error, :no_recipient_email} ->
{:noreply, put_flash(socket, :error, "No recipient email address")}
{:error, reason} ->
{:noreply,
put_flash(socket, :error, "Failed to send payment confirmation: #{inspect(reason)}")}
end
end
def void_invoice(socket) do
case Billing.void_invoice(socket.assigns.invoice) do
{:ok, invoice} ->
{:noreply,
socket
|> Phoenix.Component.assign(:invoice, invoice)
|> put_flash(:info, "Invoice voided")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to void invoice: #{reason}")}
end
end
def generate_receipt(socket) do
case Billing.generate_receipt(socket.assigns.invoice) do
{:ok, invoice} ->
{:noreply,
socket
|> Phoenix.Component.assign(:invoice, invoice)
|> put_flash(:info, "Receipt generated: #{invoice.receipt_number}")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to generate receipt: #{reason}")}
end
end
# Private helpers
defp reload_invoice(socket) do
invoice = socket.assigns.invoice
updated_invoice = Billing.get_invoice(invoice.uuid, preload: [:user, :order, :transactions])
transactions = Billing.list_invoice_transactions(invoice.uuid)
socket
|> Phoenix.Component.assign(:invoice, updated_invoice)
|> Phoenix.Component.assign(:transactions, transactions)
end
defp update_transaction_in_list(transactions, updated_transaction) do
Enum.map(transactions, fn t ->
if t.uuid == updated_transaction.uuid, do: updated_transaction, else: t
end)
end
end