defmodule DaProductApp.Adapters.SandboxPartner do @moduledoc """ Sandbox UPI International partner returning simulated responses. Simulates an international partner in Singapore corridor: - Receives SGD after PSP converts INR → SGD - Credits local merchant in SGD - Handles FX-aware QR generation and transactions """ @behaviour DaProductApp.Adapters.InternationalPartnerBehaviour @impl true def credit_merchant(params) do # Simulate crediting international merchant in local currency # PSP has already received INR from NPCI and converted to SGD {:ok, %{ code: "CS", payload: %{ partner_status: "PENDING", partner_txn_id: "SGP_#{:rand.uniform(999999)}", local_amount: params.base_amount, local_currency: params.base_currency, inr_equivalent: params.inr_amount, fx_rate_applied: params.fx_rate, merchant_account_credited: true, settlement_status: "PROCESSING" } }} end @impl true def check_transaction_status(params) do # Simulate checking status with international partner {:ok, %{ code: "CS", payload: %{ partner_status: "SUCCESS", settlement_status: "COMPLETED", merchant_balance_updated: true, local_amount_settled: params.base_amount, partner_txn_id: params.partner_txn_id } }} end @impl true def reverse_payment(params) do # Simulate reversing payment from international merchant # Return foreign currency back to PSP for INR reversal to customer {:ok, %{ code: "00", payload: %{ partner_status: "REVERSED", settlement_status: "REVERSED", reversal_completed: true, foreign_amount_reversed: params.base_amount, inr_reversal_required: params.inr_amount } }} end @impl true def generate_dynamic_qr_with_fx(params) do # Simulate generating international QR with FX details embedded # This would be called by merchant via partner's system base_amount = params.base_amount || simulate_fx_conversion(params.inr_amount) # Build UPI International QR string with FX parameters qr_data = build_international_qr(%{ merchant_vpa: params.merchant_vpa, merchant_name: params.merchant_name, base_amount: base_amount, base_currency: "SGD", fx_rate: params.fx_rate || "0.0165", markup: params.markup || "2.50", inr_amount: params.inr_amount }) {:ok, %{ code: "00", payload: %{ qr_code: qr_data, qr_image_url: "https://api.qrserver.com/v1/create-qr-code/?data=#{URI.encode(qr_data)}", expires_at: DateTime.add(DateTime.utc_now(), 15, :minute), fx_details: %{ base_amount: base_amount, base_currency: "SGD", fx_rate: params.fx_rate || "0.0165", markup_rate: params.markup || "2.50", inr_equivalent: params.inr_amount }, corridor: "SINGAPORE", partner_ref: "QR_#{:rand.uniform(999999)}" } }} end @impl true def validate_fx_rates(_params) do # Simulate FX rate validation for the corridor {:ok, %{ code: "00", payload: %{ rates_valid: true, current_rate: "0.0165", markup_rate: "2.50", rate_expires_at: DateTime.add(DateTime.utc_now(), 5, :minute), corridor: "SINGAPORE" } }} end @impl true def get_corridor_info() do %{ currency: "SGD", country: "Singapore", corridor_code: "SGP", supported_features: ["dynamic_qr", "fx_conversion", "real_time_settlement"] } end # Private helper functions defp simulate_fx_conversion(inr_amount) when is_binary(inr_amount) do {inr_float, _} = Float.parse(inr_amount) simulate_fx_conversion(inr_float) end defp simulate_fx_conversion(inr_amount) when is_number(inr_amount) do # Simulate INR to SGD conversion: 1 INR = 0.0165 SGD + 2.5% markup sgd_rate = 0.0165 markup = 0.025 base_sgd = inr_amount * sgd_rate sgd_with_markup = base_sgd * (1 + markup) Float.round(sgd_with_markup, 2) end defp build_international_qr(fx_details) do # Build UPI International QR with embedded FX parameters per UPI spec base_qr = "upi://pay?pa=#{fx_details.merchant_vpa}&pn=#{URI.encode(fx_details.merchant_name)}" fx_params = [ "am=#{fx_details.base_amount}", # Base amount in local currency "cu=#{fx_details.base_currency}", # Base currency (SGD/USD/AED) "fx=#{fx_details.fx_rate}", # FX rate "mkup=#{fx_details.markup}", # Markup percentage "inr=#{fx_details.inr_amount}", # INR equivalent for customer "mode=17", # UPI International mode "purpose=10" # International merchant payment ] "#{base_qr}&#{Enum.join(fx_params, "&")}" end end