defmodule DaProductApp.Transactions.PosTransaction do @moduledoc """ POS Transaction schema - Main transaction records. Ported from Java entity: org.jpos.tcpay.db.entity.PosTransaction Stores completed/processed transactions from all networks (YSP, VISA, MasterCard, etc.) Elixir best practices applied: - Uses Elixir naming conventions (snake_case) - Leverages Ecto changesets for validation - Implements proper associations - Uses Phoenix.Param for URL generation """ use Ecto.Schema import Ecto.Changeset alias DaProductApp.YSP.AcquirerTerminal # Configure auto-increment primary key for MySQL @primary_key {:id, :id, autogenerate: true} @derive {Phoenix.Param, key: :id} schema "pos_transaction" do # Switch-side identifiers (what POS sends) field :s_tid, :string # Switch Terminal ID field :s_mid, :string # Switch Merchant ID field :s_tid_stan, :string # Switch STAN field :s_tid_invoice_no, :string # Switch Invoice Number field :s_tid_batch_no, :string # Switch Batch Number # Backend-side identifiers (what we send to acquirer) field :b_tid, :string # Backend Terminal ID field :b_mid, :string # Backend Merchant ID # acquirer_id is defined by belongs_to association below field :b_tid_stan, :string # Backend STAN field :b_tid_invoice_no, :string # Backend Invoice Number field :b_tid_batch_no, :string # Backend Batch Number field :b_tid_date, :string # Backend Transaction Date (YYYYMMDD) field :b_tid_time, :string # Backend Transaction Time (HHMMSS) # Transaction details field :entry_mode, :string # Card entry mode (swipe, chip, manual, etc.) field :condition_code, :string # POS condition code field :currency_code, :string # Transaction currency (ISO 4217) field :mti, :string # Message Type Indicator field :proc_code, :string # Processing code # Amounts (stored as integers in minor units, e.g., cents) field :total_amount, :decimal # Total transaction amount field :auth_amount, :decimal # Authorized amount field :cash_amount, :decimal # Cash back amount field :tip_amount, :decimal # Tip amount # Response data field :approval_code, :string # Authorization code field :reference_no, :string # Reference number (RRN) field :response_code, :string # Response code (00=approved, etc.) field :response_message, :string # Response message text # Card data (encrypted/masked) field :masked_pan, :string # Masked PAN for display field :card_holder_name, :string # Cardholder name field :expiry_date, :string # Card expiry (YYMM) field :card_brand, :string # Card brand (VISA, MC, AMEX, etc.) # Security and tracking field :track2_data_hash, :string # Hashed track 2 data (for duplicate detection) field :pin_verified, :boolean # PIN verification result field :signature_required, :boolean # Signature required flag # Processing metadata field :processing_time, :integer # Processing time in milliseconds field :retry_count, :integer # Number of retry attempts field :original_mti, :string # Original MTI (for reversals/adjustments) # Status tracking field :status, :string # Transaction status field :settled, :boolean, default: false # Settlement status field :settled_at, :utc_datetime # Settlement timestamp # Associations belongs_to :acquirer_terminal, AcquirerTerminal, foreign_key: :acquirer_id, references: :id timestamps() end @doc false def changeset(pos_transaction, attrs) do pos_transaction |> cast(attrs, [ :s_tid, :s_mid, :s_tid_stan, :s_tid_invoice_no, :s_tid_batch_no, :b_tid, :b_mid, :b_tid_stan, :b_tid_invoice_no, :b_tid_batch_no, :b_tid_date, :b_tid_time, :entry_mode, :condition_code, :currency_code, :mti, :proc_code, :total_amount, :auth_amount, :cash_amount, :tip_amount, :approval_code, :reference_no, :response_code, :response_message, :masked_pan, :card_holder_name, :expiry_date, :card_brand, :track2_data_hash, :pin_verified, :signature_required, :processing_time, :retry_count, :original_mti, :status, :settled, :settled_at ]) |> validate_required([ :s_tid, :s_mid, :mti, :proc_code, :total_amount, :currency_code, :status ]) |> validate_inclusion(:status, ["PENDING", "APPROVED", "DECLINED", "REVERSED", "SETTLED"]) |> validate_inclusion(:mti, ["0100", "0110", "0200", "0210", "0400", "0410", "0420", "0430"]) |> validate_length(:s_tid, max: 8) |> validate_length(:s_mid, max: 15) |> validate_length(:response_code, is: 2) |> validate_number(:total_amount, greater_than_or_equal_to: 0) |> foreign_key_constraint(:acquirer_id) end @doc """ Creates a changeset for marking transaction as settled. """ def settle_changeset(pos_transaction, attrs \\ %{}) do pos_transaction |> cast(attrs, [:settled, :settled_at]) |> put_change(:settled, true) |> put_change(:settled_at, DateTime.utc_now()) end @doc """ Creates a changeset for updating transaction status. """ def status_changeset(pos_transaction, status) when is_binary(status) do pos_transaction |> cast(%{status: status}, [:status]) |> validate_required([:status]) |> validate_inclusion(:status, ["PENDING", "APPROVED", "DECLINED", "REVERSED", "SETTLED"]) end end