defmodule DaProductApp.MercuryISO8583 do @moduledoc """ Main API module for Mercury ISO8583 message processing. This module provides a clean interface for encoding/decoding ISO8583 messages specifically designed for Mercury payment device communication. ## Features - Direct binary processing (no ASCII conversion overhead) - TPDU extraction and handling - BCD MTI encoding/decoding - Field access by number or name - Comprehensive error handling - Debugging support ## Usage # Decode binary message {:ok, message} = MercuryISO8583.decode(binary_data) # Get field values amount = MercuryISO8583.get(message, "4") amount = MercuryISO8583.get(message, :amount) # Set field values message = MercuryISO8583.set(message, "4", "000000005000") message = MercuryISO8583.set(message, :amount, "5000") # Encode message {:ok, binary} = MercuryISO8583.encode(message) """ alias DaProductApp.MercuryISO8583.{Message, Decoder, Encoder, Utils} @doc """ Decode binary ISO8583 message. Automatically handles: - TPDU extraction (Mercury format: 6000782000) - BCD MTI decoding - Binary bitmap parsing - Field extraction and validation ## Examples iex> MercuryISO8583.decode(binary_data) {:ok, %Message{mti: "0800", fields: %{"11" => "646465", "70" => "001"}, ...}} iex> MercuryISO8583.decode(invalid_data) {:error, "MTI decode error: Invalid BCD data"} """ def decode(binary_data) when is_binary(binary_data) do Decoder.decode(binary_data) end @doc """ Decode binary message with detailed step-by-step information. Useful for debugging and troubleshooting message parsing issues. ## Examples iex> MercuryISO8583.decode_detailed(binary_data) {:ok, message, [ %{action: :extract_tpdu, tpdu_hex: "6000782000", ...}, %{action: :decode_mti, mti: "0800", ...}, %{action: :decode_bitmap, present_fields: ["7", "11", "70"], ...}, ... ]} """ def decode_detailed(binary_data) when is_binary(binary_data) do Decoder.decode_detailed(binary_data) end @doc """ Encode ISO8583 message to binary format. Automatically handles: - MTI BCD encoding - Bitmap generation from present fields - Field packing with proper length indicators - TPDU prepending (if present in message) ## Examples iex> message = %Message{mti: "0800", fields: %{"11" => "646465", "70" => "001"}} iex> MercuryISO8583.encode(message) {:ok, <>} iex> MercuryISO8583.encode(%{"0" => "0800", "11" => "646465", "70" => "001"}) {:ok, <>} """ def encode(%Message{} = message) do Encoder.encode(message) end def encode(fields_map) when is_map(fields_map) do Encoder.encode(fields_map) end @doc """ Encode message with detailed step-by-step information. Useful for debugging encoding issues. ## Examples iex> MercuryISO8583.encode_detailed(message) {:ok, binary_data, [ %{action: :validate_message, result: :success, ...}, %{action: :encode_mti, mti: "0800", mti_hex: "0800", ...}, %{action: :encode_bitmap, bitmap_hex: "8238000000000400", ...}, ... ]} """ def encode_detailed(%Message{} = message) do Encoder.encode_detailed(message) end def encode_detailed(fields_map) when is_map(fields_map) do message = Message.from_map(fields_map) Encoder.encode_detailed(message) end @doc """ Get field value from message. Supports both field numbers and field names: - Field numbers: "4", 4 - Field names: :amount, :stan, :pan ## Examples iex> MercuryISO8583.get(message, "4") "000000005000" iex> MercuryISO8583.get(message, :amount) "000000005000" iex> MercuryISO8583.get(message, "99") nil """ def get(%Message{} = message, field) do Message.get_field(message, field) end def get(fields_map, field) when is_map(fields_map) do field_str = Utils.field_to_string(field) case field_str do "0" -> Map.get(fields_map, "0") _ -> Map.get(fields_map, field_str) end end @doc """ Set field value in message. Supports both field numbers and field names: - Field numbers: "4", 4 - Field names: :amount, :stan, :pan ## Examples iex> MercuryISO8583.set(message, "4", "000000005000") %Message{...} iex> MercuryISO8583.set(message, :amount, "5000") %Message{...} """ def set(%Message{} = message, field, value) do Message.set_field(message, field, value) end def set(fields_map, field, value) when is_map(fields_map) do field_str = Utils.field_to_string(field) Map.put(fields_map, field_str, value) end @doc """ Remove field from message. ## Examples iex> MercuryISO8583.remove(message, "70") %Message{...} iex> MercuryISO8583.remove(message, :echo_data) %Message{...} """ def remove(%Message{} = message, field) do Message.remove_field(message, field) end def remove(fields_map, field) when is_map(fields_map) do field_str = Utils.field_to_string(field) Map.delete(fields_map, field_str) end @doc """ Check if field is present in message. ## Examples iex> MercuryISO8583.has_field?(message, "4") true iex> MercuryISO8583.has_field?(message, :amount) true """ def has_field?(%Message{} = message, field) do Message.has_field?(message, field) end def has_field?(fields_map, field) when is_map(fields_map) do field_str = Utils.field_to_string(field) Map.has_key?(fields_map, field_str) end @doc """ Get all present field numbers in the message. ## Examples iex> MercuryISO8583.present_fields(message) ["0", "7", "11", "70"] """ def present_fields(%Message{} = message) do Message.present_fields(message) end def present_fields(fields_map) when is_map(fields_map) do Map.keys(fields_map) |> Enum.sort_by(&Utils.field_to_integer/1) end @doc """ Create a new message with the given MTI. ## Examples iex> MercuryISO8583.new("0800") %Message{mti: "0800", fields: %{}, ...} """ def new(mti \\ nil) do Message.new(mti) end @doc """ Create message from map of fields. ## Examples iex> MercuryISO8583.from_map(%{"0" => "0800", "11" => "646465"}) %Message{mti: "0800", fields: %{"11" => "646465"}, ...} """ def from_map(fields_map) when is_map(fields_map) do Message.from_map(fields_map) end @doc """ Convert message to map format. ## Examples iex> MercuryISO8583.to_map(message) %{"0" => "0800", "11" => "646465", "70" => "001"} """ def to_map(%Message{} = message) do Message.to_map(message) end @doc """ Validate message structure and field content. ## Examples iex> MercuryISO8583.validate(message) {:ok, message} iex> MercuryISO8583.validate(invalid_message) {:error, "MTI is required"} """ def validate(%Message{} = message) do Message.validate(message) end def validate(fields_map) when is_map(fields_map) do message = Message.from_map(fields_map) Message.validate(message) end @doc """ Create response message from request message. Automatically: - Generates appropriate response MTI - Copies required fields from request - Sets response code ## Examples iex> MercuryISO8583.create_response(request_message, "00") %Message{mti: "0810", fields: %{"39" => "00", ...}, ...} """ def create_response(%Message{} = request_message, response_code \\ "00") do Message.create_response(request_message, response_code) end @doc """ Encode response message for a request. ## Examples iex> MercuryISO8583.encode_response(request_message, "00") {:ok, binary_response} """ def encode_response(%Message{} = request_message, response_code \\ "00", additional_fields \\ %{}) do Encoder.encode_response(request_message, response_code, additional_fields) end @doc """ Quick creation and encoding of network management request. ## Examples iex> MercuryISO8583.network_management_request("646465", "001") {:ok, binary_message} """ def network_management_request(stan, echo_data \\ "001") do Encoder.encode_network_management_request(stan, echo_data) end @doc """ Quick creation and encoding of authorization request. ## Examples iex> MercuryISO8583.authorization_request("4761739001010119", "5000", "646465") {:ok, binary_message} """ def authorization_request(pan, amount, stan, additional_fields \\ %{}) do Encoder.encode_authorization_request(pan, amount, stan, additional_fields) end @doc """ Decode message with TCP length header. ## Examples iex> MercuryISO8583.decode_with_length_header(<<0, 50, binary_message::binary>>) {:ok, message} """ def decode_with_length_header(binary_data) when is_binary(binary_data) do Decoder.decode_with_length_header(binary_data) end @doc """ Encode message with TCP length header. ## Examples iex> MercuryISO8583.encode_with_length_header(message) {:ok, <<0, 50, binary_message::binary>>} """ def encode_with_length_header(%Message{} = message) do Encoder.encode_with_length_header(message) end def encode_with_length_header(fields_map) when is_map(fields_map) do Encoder.encode_with_length_header(fields_map) end @doc """ Get field name for field number (if available). ## Examples iex> MercuryISO8583.field_name("4") :amount iex> MercuryISO8583.field_name("99") "99" """ def field_name(field) do Utils.field_alias(field) end @doc """ Get field number for field name. ## Examples iex> MercuryISO8583.field_number(:amount) "4" iex> MercuryISO8583.field_number(:unknown) "unknown" """ def field_number(field) do Utils.field_alias(field) end @doc """ Get message type description. ## Examples iex> MercuryISO8583.message_type(message) "Network Management Request" """ def message_type(%Message{mti: mti}) do DaProductApp.MercuryISO8583.MTI.description(mti) end def message_type(mti) when is_binary(mti) do DaProductApp.MercuryISO8583.MTI.description(mti) end @doc """ Check if message is a request. ## Examples iex> MercuryISO8583.request?(message) true """ def request?(%Message{} = message) do Message.request?(message) end def request?(mti) when is_binary(mti) do DaProductApp.MercuryISO8583.MTI.request?(mti) end @doc """ Check if message is a response. ## Examples iex> MercuryISO8583.response?(message) false """ def response?(%Message{} = message) do Message.response?(message) end def response?(mti) when is_binary(mti) do DaProductApp.MercuryISO8583.MTI.response?(mti) end end