# Phase 5B: UPI International Partner Integration Plan

## What We'll Build: UPI International PSP

### **Architecture Overview:**
```
Indian Customer (abroad) → Scans International QR → NPCI UPI → YOUR PSP → International Partner → International Merchant
```

### **Key Features:**
- ✅ **International Transactions** with FX conversion
- ✅ **Partner-based merchant management** (like domestic UPI)
- ✅ **Dynamic QR generation** with FX rates embedded
- ✅ **Multi-currency support** (USD, EUR, SGD, etc.)
- ✅ **Real-time FX calculation** and markup handling

### 1. Enhanced Schemas with FX Support

#### **FX Rate Schema:**
```elixir
defmodule DaProductApp.ForeignExchange.FxRate do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}

  schema "fx_rates" do
    field :base_currency, :string      # "INR" 
    field :target_currency, :string    # "USD", "EUR", "SGD"
    field :fx_rate, :decimal           # Exchange rate (e.g., 0.012 INR to USD)
    field :markup_rate, :decimal       # PSP markup percentage (e.g., 2.50%)
    field :effective_from, :utc_datetime
    field :effective_until, :utc_datetime
    field :active, :boolean, default: true
    field :source, :string             # "RBI", "PARTNER", "MANUAL"
    field :last_modified_ts, :utc_datetime

    timestamps(type: :utc_datetime)
  end

  def changeset(fx_rate, attrs) do
    fx_rate
    |> cast(attrs, [:base_currency, :target_currency, :fx_rate, :markup_rate, 
                    :effective_from, :effective_until, :active, :source])
    |> validate_required([:base_currency, :target_currency, :fx_rate])
    |> validate_number(:fx_rate, greater_than: 0)
    |> validate_number(:markup_rate, greater_than_or_equal_to: 0)
  end
end
```

#### **Enhanced QR Validation with FX:**
```elixir
defmodule DaProductApp.QrValidations.QRValidation do
  use Ecto.Schema
  import Ecto.Changeset

  schema "qr_validations" do
    # Existing fields...
    field :payee_vpa, :string
    field :payee_name, :string
    field :amount, :decimal
    field :currency, :string
    
    # NEW: International FX fields
    field :base_amount, :decimal       # Amount in merchant's local currency
    field :base_currency, :string      # Merchant's currency (USD, EUR, etc.)
    field :fx_rate, :decimal           # Applied exchange rate
    field :markup_rate, :decimal       # PSP markup percentage
    field :inr_amount, :decimal        # Calculated INR amount for customer
    field :fx_timestamp, :utc_datetime # When FX rate was locked
    field :corridor, :string           # "SINGAPORE", "UAE", "USA", etc.
    
    belongs_to :partner, DaProductApp.Partners.Partner
    belongs_to :merchant, DaProductApp.Partners.Merchant
    
    timestamps(type: :utc_datetime)
  end
end
```

### 2. International Partner Adapters with FX

```elixir
defmodule DaProductApp.Adapters.SingaporePartner do
  @behaviour DaProductApp.Adapters.PartnerAcquirerBehaviour
  
  @base_url "https://api.singapore-partner.com/v1"
  
  def generate_dynamic_qr(params) do
    # Get current FX rates for SGD
    fx_rate = FxRateService.get_current_rate("INR", "SGD")
    
    # Calculate SGD amount with markup
    sgd_amount = calculate_local_amount(params.inr_amount, fx_rate)
    
    payload = %{
      "merchant_id" => params.merchant_id,
      "amount" => %{
        "value" => sgd_amount,
        "currency" => "SGD"
      },
      "fx_details" => %{
        "base_amount" => sgd_amount,
        "base_currency" => "SGD", 
        "fx_rate" => fx_rate.fx_rate,
        "markup" => fx_rate.markup_rate,
        "inr_equivalent" => params.inr_amount
      },
      "qr_type" => "international_upi",
      "expires_in" => 900  # 15 minutes
    }
    
    case HTTPoison.post("#{@base_url}/qr/generate", Jason.encode!(payload), headers()) do
      {:ok, %{status_code: 200, body: body}} ->
        response = Jason.decode!(body)
        
        # UPI International QR format with FX embedded
        qr_string = build_international_qr(response, params.fx_details)
        
        {:ok, %{
          code: "00",
          payload: %{
            qr_code: qr_string,
            qr_image_url: response["qr_image"],
            base_amount: sgd_amount,
            base_currency: "SGD",
            fx_rate: fx_rate.fx_rate,
            markup: fx_rate.markup_rate,
            inr_amount: params.inr_amount,
            expires_at: DateTime.add(DateTime.utc_now(), 15, :minute)
          }
        }}
        
      error -> {:error, error}
    end
  end
  
  def credit_merchant(params) do
    # Credit merchant in SGD after INR debit successful
    sgd_amount = params.base_amount  # Already calculated during QR generation
    
    payload = %{
      "account_id" => params.merchant_account_id,
      "amount" => sgd_amount,
      "currency" => "SGD",
      "reference" => params.org_txn_id,
      "inr_debit_amount" => params.inr_amount,
      "fx_rate_applied" => params.fx_rate
    }
    
    case HTTPoison.post("#{@base_url}/transfers", Jason.encode!(payload), headers()) do
      {:ok, %{status_code: 200, body: body}} ->
        {:ok, %{code: "CS", payload: Jason.decode!(body)}}
      error -> {:error, error}
    end
  end
  
  defp build_international_qr(partner_response, fx_details) do
    # Build UPI International QR with embedded FX parameters
    base_qr = "upi://pay?pa=#{partner_response["merchant_vpa"]}&pn=#{partner_response["merchant_name"]}"
    
    fx_params = [
      "cu=#{fx_details.base_currency}",      # Base currency 
      "am=#{fx_details.base_amount}",        # Base amount
      "fx=#{fx_details.fx_rate}",            # FX rate
      "mkup=#{fx_details.markup}",           # Markup
      "inr=#{fx_details.inr_amount}"         # INR equivalent
    ]
    
    "#{base_qr}&#{Enum.join(fx_params, "&")}"
  end
end
```

### 3. FX Rate Service

```elixir
defmodule DaProductApp.ForeignExchange.FxRateService do
  @moduledoc """
  Service for managing FX rates and currency conversions.
  Supports real-time rate fetching and caching.
  """
  
  def get_current_rate(from_currency, to_currency) do
    case Repo.get_by(FxRate, 
                     base_currency: from_currency, 
                     target_currency: to_currency,
                     active: true) do
      nil -> fetch_live_rate(from_currency, to_currency)
      rate -> rate
    end
  end
  
  def calculate_inr_amount(base_amount, base_currency) do
    fx_rate = get_current_rate(base_currency, "INR")
    
    # INR = Base Amount × FX Rate × (1 + Markup)
    inr_before_markup = Decimal.mult(base_amount, fx_rate.fx_rate)
    markup_amount = Decimal.mult(inr_before_markup, fx_rate.markup_rate)
    
    Decimal.add(inr_before_markup, markup_amount)
  end
  
  def calculate_base_amount(inr_amount, target_currency) do
    fx_rate = get_current_rate("INR", target_currency)
    
    # Remove markup first, then convert
    markup_multiplier = Decimal.add(1, fx_rate.markup_rate)
    inr_without_markup = Decimal.div(inr_amount, markup_multiplier)
    
    Decimal.mult(inr_without_markup, fx_rate.fx_rate)
  end
  
  defp fetch_live_rate(from_currency, to_currency) do
    # Integrate with live FX providers (RBI, partner APIs, etc.)
    case ExchangeRateAPI.get_rate(from_currency, to_currency) do
      {:ok, live_rate} -> 
        cache_rate(from_currency, to_currency, live_rate)
      {:error, _} -> 
        get_fallback_rate(from_currency, to_currency)
    end
  end
end
```

### 4. Enhanced XML Parser with FX Support

```elixir
# Update UpiXmlParser to handle FX fields
defmodule DaProductApp.Parsers.UpiXmlParser do
  def parse_fx_list(xml_doc) do
    xml_doc
    |> xpath(~x"//FxList/Fx"l)
    |> Enum.map(fn fx_node ->
      %{
        base_amount: fx_node |> xpath(~x"./@baseAmount"s),
        base_currency: fx_node |> xpath(~x"./@baseCurr"s),
        active: fx_node |> xpath(~x"./@active"s) == "Y",
        fx_rate: fx_node |> xpath(~x"./@Fx"s) |> parse_decimal(),
        markup: fx_node |> xpath(~x"./@Mkup"s) |> parse_decimal(),
        last_modified: fx_node |> xpath(~x"./@lastModifedTs"s)
      }
    end)
  end
  
  def parse_international_qr_validation(xml) do
    basic_validation = parse_qr_validation(xml)
    fx_list = parse_fx_list(xml)
    
    Map.merge(basic_validation, %{
      fx_rates: fx_list,
      corridor: extract_corridor(xml),
      international: true
    })
  end
end
```

### 3. HTTP Client with Resilience
```elixir
defmodule DaProductApp.PartnerClient do
  @retry_count 3
  @base_timeout 30_000
  
  def call(partner_adapter, method, params) do
    Enum.reduce_while(1..@retry_count, nil, fn attempt, _acc ->
      case apply(partner_adapter, method, [params]) do
        {:ok, result} -> {:halt, {:ok, result}}
        {:error, :timeout} when attempt < @retry_count ->
          :timer.sleep(backoff_delay(attempt))
          {:cont, nil}
        {:error, reason} -> {:halt, {:error, reason}}
      end
    end)
  end
  
  defp backoff_delay(attempt), do: :math.pow(2, attempt) * 1000 |> round()
end
```

### 5. Configuration for International Corridors

```elixir
# config/prod.exs
config :da_product_app, :international_corridors,
  singapore: %{
    adapter: DaProductApp.Adapters.SingaporePartner,
    base_currency: "SGD",
    api_endpoint: "https://api.singapore-partner.com/v1",
    credentials: %{
      partner_id: {:system, "SINGAPORE_PARTNER_ID"},
      api_key: {:system, "SINGAPORE_API_KEY"}
    }
  },
  uae: %{
    adapter: DaProductApp.Adapters.UAEPartner,
    base_currency: "AED", 
    api_endpoint: "https://api.uae-partner.com/v1",
    credentials: %{
      partner_id: {:system, "UAE_PARTNER_ID"},
      api_key: {:system, "UAE_API_KEY"}
    }
  },
  usa: %{
    adapter: DaProductApp.Adapters.USAPartner,
    base_currency: "USD",
    api_endpoint: "https://api.usa-partner.com/v1"
  }

# FX Rate providers
config :da_product_app, :fx_providers,
  primary: %{
    provider: "RBI_REFERENCE_RATE",
    api_url: "https://rbi.org.in/api/fx-rates"
  },
  fallback: %{
    provider: "PARTNER_RATES",
    refresh_interval: 300  # 5 minutes
  }
```

## Real-World UPI International Scenarios

### 1. **Dynamic QR with FX Calculation**
```
1. Merchant (Singapore Coffee Shop) requests ₹500 payment
2. Your PSP fetches SGD exchange rate: 1 INR = 0.0165 SGD
3. Applies 2.5% markup: Final rate = 0.0169 SGD
4. Calculates: SGD 8.45 + markup = SGD 8.66
5. Generates QR with embedded FX: "upi://pay?pa=merchant@sgp&am=8.66&cu=SGD&fx=0.0165&mkup=2.5&inr=500"
6. Customer scans → UPI app shows: "Pay SGD 8.66 (₹500 + markup)"
```

### 2. **Network Timeout Scenarios**
```
1. Customer pays ₹1000 for USD 12.50 transaction
2. INR debit successful from customer account
3. Partner API timeout during USD credit to merchant
4. Your system triggers ChkTxn with FX details
5. If still pending after 30s → Reversal with currency conversion
6. INR reversal amount = USD reversal × current FX rate
```

### 3. **FX Rate Expiry Handling**
```
1. QR generated at 10:00 AM with FX rate 83.50 INR/USD
2. Customer scans at 10:16 AM (QR expired at 10:15)
3. Your system fetches fresh FX rate: 83.75 INR/USD
4. Recalculates amounts and regenerates QR
5. Ensures merchant gets exact USD amount originally intended
```

## Benefits of UPI International Integration

✅ **Multi-Currency Support**: Handle 15+ international corridors
✅ **Real-time FX**: Live exchange rates with markup transparency  
✅ **Regulatory Compliance**: FEMA compliance for outward remittance
✅ **Partner Ecosystem**: Integrate with established international acquirers
✅ **Customer Experience**: Seamless international payments via familiar UPI
✅ **Settlement Efficiency**: Automated FX conversion and partner settlement

## Implementation Priority

### **Phase 5B-1: Core FX Infrastructure**
1. **FX Rate Management** - Live rate fetching and caching
2. **Currency Conversion** - Accurate calculations with markup
3. **Enhanced Schemas** - Support for international fields
4. **XML Parser Updates** - Handle FxList and international tags

### **Phase 5B-2: Partner Integration**  
1. **Singapore Corridor** - First international partner
2. **Dynamic QR with FX** - Embedded exchange rate information
3. **Settlement Logic** - Multi-currency transaction handling
4. **Error Scenarios** - FX-aware timeout and reversal logic

### **Phase 5B-3: Additional Corridors**
1. **UAE/AED Support** - Second corridor implementation
2. **USA/USD Support** - High-value corridor
3. **Multi-Partner Routing** - Route by corridor/currency
4. **Performance Optimization** - Handle high international volume

## Real Partner Examples

### **International Partners You'll Integrate:**
- **Singapore**: DBS Bank, OCBC, UOB (local acquirers)
- **UAE**: Emirates NBD, ADCB, FAB (local banking partners)
- **USA**: Visa Direct, Mastercard Send (card network partners)
- **Bhutan**: Bank of Bhutan, Bhutan National Bank
- **Nepal**: Nepal SBI, Himalayan Bank

### **Technical Integration Points:**
1. **Partner APIs**: HTTP-based merchant crediting
2. **QR Generation**: Partner-specific QR formats with UPI extensions
3. **Settlement**: Multi-currency reconciliation
4. **Compliance**: KYC/AML checks per corridor regulations
5. **Monitoring**: FX rate monitoring and partner uptime tracking
