defmodule DaProductApp.Transactions.Operations do @moduledoc """ Context module for managing transaction operations (refunds, cancellations, voids, reversals) """ import Ecto.Query alias DaProductApp.Repo alias DaProductApp.Transactions.{Transaction, TransactionOperation} @doc """ Creates a refund operation for a transaction """ def create_refund(transaction_id, attrs) do attrs |> Map.put(:operation_type, "refund") |> Map.put(:status, "pending") |> create_operation(transaction_id) end @doc """ Creates a cancel operation for a transaction """ def create_cancel(transaction_id, attrs) do attrs |> Map.put(:operation_type, "cancel") |> Map.put(:status, "pending") |> create_operation(transaction_id) end @doc """ Creates a void operation for a transaction """ def create_void(transaction_id, attrs) do attrs |> Map.put(:operation_type, "void") |> Map.put(:status, "pending") |> create_operation(transaction_id) end @doc """ Creates a reversal operation for a transaction """ def create_reversal(transaction_id, attrs) do attrs |> Map.put(:operation_type, "reversal") |> Map.put(:status, "pending") |> create_operation(transaction_id) end @doc """ Updates an operation status and details """ def update_operation(operation, attrs) do operation |> TransactionOperation.changeset(attrs) |> Repo.update() end @doc """ Gets all operations for a transaction """ def list_operations(transaction_id) do from(op in TransactionOperation, where: op.transaction_id == ^transaction_id, order_by: [desc: op.inserted_at]) |> Repo.all() end @doc """ Gets operations by type for a transaction """ def list_operations_by_type(transaction_id, operation_type) do from(op in TransactionOperation, where: op.transaction_id == ^transaction_id and op.operation_type == ^operation_type, order_by: [desc: op.inserted_at]) |> Repo.all() end @doc """ Gets successful refunds for a transaction """ def list_successful_refunds(transaction_id) do from(op in TransactionOperation, where: op.transaction_id == ^transaction_id and op.operation_type == "refund" and op.status == "success", order_by: [desc: op.inserted_at]) |> Repo.all() end @doc """ Calculates total refunded amount for a transaction """ def calculate_total_refunded(transaction_id) do successful_refunds = list_successful_refunds(transaction_id) successful_refunds |> Enum.reduce(0, fn refund, acc -> case refund.operation_amount_value do nil -> acc amount_str -> case Float.parse(amount_str) do {amount, _} -> acc + amount _ -> acc end end end) end @doc """ Checks if a transaction can be refunded (not already fully refunded or cancelled) """ def can_refund?(transaction_id, refund_amount) do case Repo.get(Transaction, transaction_id) do nil -> false transaction -> case transaction.status do "success" -> total_refunded = calculate_total_refunded(transaction_id) remaining_amount = Decimal.to_float(transaction.transaction_amount) - total_refunded refund_amount <= remaining_amount _ -> false end end end @doc """ Checks if a transaction can be cancelled """ def can_cancel?(transaction_id) do case Repo.get(Transaction, transaction_id) do nil -> false transaction -> transaction.status in ["pending", "qr_generated"] end end @doc """ Gets the latest operation of a specific type for a transaction """ def get_latest_operation(transaction_id, operation_type) do from(op in TransactionOperation, where: op.transaction_id == ^transaction_id and op.operation_type == ^operation_type, order_by: [desc: op.inserted_at], limit: 1) |> Repo.one() end defp create_operation(attrs, transaction_id) do %TransactionOperation{} |> TransactionOperation.changeset(Map.put(attrs, :transaction_id, transaction_id)) |> Repo.insert() end end