# Field Validation Sequence - Implementation Guide

## Overview

The field validation sequence feature allows you to validate and adjust ISO8583 response fields before sending them to devices. This provides a centralized place to enforce field-level business rules, data quality checks, and automatic corrections.

## Architecture

```
Response Message
    ↓
validate_and_adjust_fields()
    ↓
Loop through validation_sequence
    ↓
For each field rule:
  - Check field value
  - Validate format/content
  - Set field if missing/invalid
  - Unset field if not needed
    ↓
Return adjusted response
    ↓
Continue with receipt/promo/etc.
```

## How It Works

### 1. Validation Sequence

Fields are validated in a specific order based on priority:

```elixir
[
  %{field: 39, action: :validate_response_code, priority: 1},
  %{field: 38, action: :validate_auth_code, priority: 2},
  %{field: 4, action: :validate_amount, priority: 3},
  %{field: 2, action: :validate_pan, priority: 4},
  %{field: 11, action: :validate_stan, priority: 5},
  %{field: 41, action: :validate_terminal_id, priority: 6},
  %{field: 42, action: :validate_merchant_id, priority: 7},
]
```

### 2. Field Processing

For each field, the system:
1. Checks if the field exists
2. Validates the field value
3. **Sets** the field if missing or invalid
4. **Unsets** the field if not needed
5. Logs any adjustments made

## Configuration

### Enable/Disable Validation

```elixir
# In channel_context:
channel_context = %{
  enable_field_validation: true,  # default: true
  # ... other settings
}
```

### Custom Validation Sequence

You can define a custom validation sequence per channel:

```elixir
channel_context = %{
  enable_field_validation: true,
  field_validation_sequence: [
    %{field: 39, action: :validate_response_code, priority: 1},
    %{field: 4, action: :validate_amount, priority: 2},
    # Add your custom rules
  ]
}
```

## Implementation Examples

### Example 1: Set Missing Auth Code

```elixir
defp validate_and_adjust_auth_code(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  response_code = get_field_safe(response, 39, "96")
  
  if response_code == "00" do
    case get_field_safe(response, field_num, nil) do
      nil -> 
        Logger.warning("Approved transaction missing auth code, generating placeholder")
        # SET: Add missing auth code
        ISOMsg.set(response, field_num, "000000")
      
      "" ->
        Logger.warning("Empty auth code for approved transaction")
        # SET: Replace empty auth code
        ISOMsg.set(response, field_num, "000000")
      
      value ->
        # Valid auth code exists
        response
    end
  else
    # For declined transactions, auth code might not be needed
    case get_field_safe(response, field_num, nil) do
      nil -> response  # Already absent, no action
      _value ->
        # UNSET: Remove auth code from declined transaction
        Logger.debug("Removing auth code from declined transaction")
        ISOMsg.unset(response, field_num)
    end
  end
end
```

### Example 2: Validate and Adjust Amount

```elixir
defp validate_and_adjust_amount(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  case get_field_safe(response, field_num, nil) do
    nil ->
      Logger.error("Amount field missing, setting to zero")
      # SET: Add missing amount
      ISOMsg.set(response, field_num, "000000000000")
    
    value ->
      case Integer.parse(value) do
        {amount, _} when amount >= 0 ->
          # Valid amount, check format
          if String.length(value) == 12 do
            response
          else
            # SET: Fix amount format (pad to 12 digits)
            padded = String.pad_leading(Integer.to_string(amount), 12, "0")
            ISOMsg.set(response, field_num, padded)
          end
        
        _ ->
          Logger.error("Invalid amount format: #{value}, setting to zero")
          # SET: Replace invalid amount
          ISOMsg.set(response, field_num, "000000000000")
      end
  end
end
```

### Example 3: Validate PAN and Mask if Needed

```elixir
defp validate_and_adjust_pan(%ISOMsg{} = response, field_num, _payload, channel_context) do
  case get_field_safe(response, field_num, nil) do
    nil ->
      Logger.warning("PAN field missing")
      # Decide: SET a placeholder or leave it missing?
      response
    
    value ->
      # Validate PAN length
      pan_length = String.length(value)
      
      cond do
        pan_length < 13 or pan_length > 19 ->
          Logger.error("Invalid PAN length: #{pan_length}")
          # UNSET: Remove invalid PAN
          ISOMsg.unset(response, field_num)
        
        # Check if channel requires PAN masking
        Map.get(channel_context, :mask_pan_in_response, false) ->
          # SET: Replace with masked PAN
          masked_pan = mask_pan(value)
          ISOMsg.set(response, field_num, masked_pan)
        
        true ->
          # Valid PAN, no changes needed
          response
      end
  end
end

defp mask_pan(pan) do
  # Mask middle digits, keep first 6 and last 4
  case String.length(pan) do
    len when len >= 10 ->
      first = String.slice(pan, 0, 6)
      last = String.slice(pan, -4, 4)
      middle_length = len - 10
      masked_middle = String.duplicate("*", middle_length)
      "#{first}#{masked_middle}#{last}"
    
    _ ->
      pan  # Too short to mask
  end
end
```

### Example 4: Conditional Field Unset

```elixir
defp validate_and_adjust_cvv(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  # CVV should NEVER be in the response for security reasons
  case get_field_safe(response, field_num, nil) do
    nil ->
      # Already absent, good
      response
    
    _value ->
      Logger.warning("CVV found in response (security risk), removing it")
      # UNSET: Remove CVV for security
      ISOMsg.unset(response, field_num)
  end
end
```

### Example 5: Set Default Values Based on Transaction Type

```elixir
defp validate_and_adjust_processing_code(%ISOMsg{} = response, field_num, payload, _channel_context) do
  case get_field_safe(response, field_num, nil) do
    nil ->
      # Determine processing code based on MTI
      mti = ISOMsg.get_mti(response)
      default_code = case mti do
        "0210" -> "000000"  # Purchase response
        "0410" -> "200000"  # Reversal response
        "0810" -> "990000"  # Network management response
        _ -> "000000"
      end
      
      Logger.debug("Setting default processing code: #{default_code} for MTI: #{mti}")
      # SET: Add default processing code
      ISOMsg.set(response, field_num, default_code)
    
    _value ->
      response
  end
end
```

## Adding New Validation Rules

### Step 1: Add to Validation Sequence

```elixir
defp get_default_validation_sequence do
  [
    # Existing rules...
    %{field: 39, action: :validate_response_code, priority: 1},
    
    # NEW: Add your custom rule
    %{field: 52, action: :validate_pin_block, priority: 8},
    %{field: 55, action: :validate_emv_data, priority: 9},
  ]
  |> Enum.sort_by(& &1.priority)
end
```

### Step 2: Add Action Handler

```elixir
defp process_field_validation_rule(%ISOMsg{} = response, field_rule, payload, channel_context) do
  field_num = Map.get(field_rule, :field)
  action = Map.get(field_rule, :action)
  
  case action do
    # Existing actions...
    :validate_response_code -> validate_and_adjust_response_code(...)
    
    # NEW: Add your action handler
    :validate_pin_block -> validate_and_adjust_pin_block(response, field_num, payload, channel_context)
    :validate_emv_data -> validate_and_adjust_emv_data(response, field_num, payload, channel_context)
    
    _ -> response
  end
end
```

### Step 3: Implement Validation Function

```elixir
defp validate_and_adjust_pin_block(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  # PIN block should NEVER be in response
  case get_field_safe(response, field_num, nil) do
    nil -> response
    _value ->
      Logger.error("PIN block found in response (security violation), removing")
      ISOMsg.unset(response, field_num)
  end
end

defp validate_and_adjust_emv_data(%ISOMsg{} = response, field_num, _payload, channel_context) do
  # Validate EMV data if present
  case get_field_safe(response, field_num, nil) do
    nil -> response
    value ->
      if valid_emv_format?(value) do
        response
      else
        Logger.warning("Invalid EMV data format, removing field")
        ISOMsg.unset(response, field_num)
      end
  end
end
```

## Advanced Usage

### Validation Based on Channel Configuration

```elixir
defp validate_and_adjust_auth_code(%ISOMsg{} = response, field_num, _payload, channel_context) do
  # Check if this channel requires auth codes
  requires_auth_code = Map.get(channel_context, :requires_auth_code, true)
  
  if requires_auth_code do
    # Ensure auth code is present
    case get_field_safe(response, field_num, nil) do
      nil -> ISOMsg.set(response, field_num, generate_auth_code())
      "" -> ISOMsg.set(response, field_num, generate_auth_code())
      _value -> response
    end
  else
    # This channel doesn't use auth codes, remove if present
    case get_field_safe(response, field_num, nil) do
      nil -> response
      _value -> ISOMsg.unset(response, field_num)
    end
  end
end
```

### Validation with Cross-Field Dependencies

```elixir
defp validate_and_adjust_auth_code(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  # Auth code depends on response code
  response_code = get_field_safe(response, 39, "96")
  auth_code = get_field_safe(response, field_num, nil)
  
  cond do
    # Approved transaction MUST have auth code
    response_code == "00" and (auth_code == nil or auth_code == "") ->
      Logger.warning("Approved transaction missing auth code")
      ISOMsg.set(response, field_num, generate_auth_code())
    
    # Declined transaction should NOT have auth code
    response_code != "00" and auth_code != nil ->
      Logger.debug("Removing auth code from declined transaction")
      ISOMsg.unset(response, field_num)
    
    # Valid state
    true ->
      response
  end
end
```

### Database-Driven Validation Rules

```elixir
defp get_field_validation_sequence(channel_context) do
  # Get terminal or merchant specific rules from database
  terminal_id = Map.get(channel_context, :terminal_id)
  
  case Acquirer.get_validation_rules(terminal_id) do
    {:ok, rules} -> 
      # Convert database rules to validation sequence
      Enum.map(rules, fn rule ->
        %{
          field: rule.field_number,
          action: String.to_atom(rule.action),
          priority: rule.priority
        }
      end)
      |> Enum.sort_by(& &1.priority)
    
    _ -> 
      get_default_validation_sequence()
  end
end
```

## Testing

### Unit Test Example

```elixir
test "validates and sets missing auth code for approved transaction" do
  # Create approved response without auth code
  response = create_test_response(%{
    mti: "0210",
    response_code: "00"
    # Note: No field 38 (auth code)
  })
  
  payload = %{upstream_response: response, channel_context: %{}}
  
  # Process validation
  adjusted = validate_and_adjust_auth_code(response, 38, payload, %{})
  
  # Verify auth code was added
  {:ok, auth_code} = ISOMsg.get(adjusted, 38)
  assert auth_code != nil
  assert String.length(auth_code) == 6
end

test "unsets auth code from declined transaction" do
  # Create declined response with auth code
  response = create_test_response(%{
    mti: "0210",
    response_code: "05",
    auth_code: "123456"  # Should be removed
  })
  
  payload = %{upstream_response: response, channel_context: %{}}
  
  # Process validation
  adjusted = validate_and_adjust_auth_code(response, 38, payload, %{})
  
  # Verify auth code was removed
  result = ISOMsg.get(adjusted, 38)
  assert result == {:error, :field_not_found} or result == nil
end
```

## Best Practices

1. **Order Matters**: Validate critical fields first (response code, amounts)
2. **Log Changes**: Always log when you set or unset fields
3. **Security First**: Remove sensitive fields (PIN, CVV) immediately
4. **Fail Safe**: If validation fails, log and continue (don't break transaction)
5. **Channel Specific**: Use channel_context to customize validation per channel
6. **Test Thoroughly**: Test all validation paths (set, unset, pass-through)
7. **Document Rules**: Comment why each validation rule exists
8. **Audit Changes**: Track what fields were modified for compliance

## Monitoring

### Logs to Watch

```bash
# Field validation activity
grep "Validating field" logs/dev.log

# Fields that were adjusted
grep "setting default\|removing field\|replacing" logs/dev.log

# Validation warnings
grep "Field.*missing\|Invalid.*format" logs/dev.log
```

### Metrics to Track

- Fields validated per transaction
- Fields set (missing → added)
- Fields unset (removed)
- Validation failures
- Validation processing time

## Security Considerations

### Always Unset These Fields in Responses:

```elixir
# Add these to your validation sequence
[
  %{field: 52, action: :remove_pin_block, priority: 1},      # PIN block
  %{field: 2, action: :mask_pan, priority: 2},               # PAN (mask it)
  %{field: 14, action: :remove_expiry, priority: 3},         # Expiry date
  %{field: 35, action: :remove_track2, priority: 4},         # Track 2 data
  %{field: 36, action: :remove_track3, priority: 5},         # Track 3 data
]
```

### Implementation:

```elixir
defp remove_sensitive_field(%ISOMsg{} = response, field_num, _payload, _channel_context) do
  case get_field_safe(response, field_num, nil) do
    nil -> response
    _value ->
      Logger.warning("Sensitive data in field #{field_num}, removing for security")
      ISOMsg.unset(response, field_num)
  end
end
```

## Troubleshooting

### Field Not Being Validated

**Check:**
1. Is field in validation sequence?
2. Is validation enabled? (`enable_field_validation: true`)
3. Is action handler implemented?

### Field Being Set Incorrectly

**Check:**
1. Validation logic in the function
2. Cross-field dependencies
3. Channel-specific requirements

### Performance Issues

**Optimize:**
1. Reduce number of fields in sequence
2. Cache database lookups
3. Skip validation for specific channels

---

## Summary

The field validation sequence provides a powerful, centralized way to:
- ✅ Validate field presence and format
- ✅ Set missing or invalid fields
- ✅ Unset unnecessary or sensitive fields
- ✅ Enforce business rules consistently
- ✅ Customize validation per channel

**Status**: Framework ready, implement validation logic as needed
