Skip to content

Remittance Schema

Owner Classification Review Date Status
CDO Office Internal April 2027 Active
-- ============================================================================
-- Simpaisa Remittances SurrealDB Schema
-- Domain: Cross-Border Remittances, FX Quotes, AML/CFT Checks,
--         Beneficiary KYC, Corridor Configuration, Compliance Records
-- Target: SurrealDB 2.x | SurrealQL
-- Markets: PK, BD, NP, IQ
-- Scale: 270M+ transactions, $1B+ processed
-- ============================================================================

-- ============================================================================
-- TABLE: remittance
-- Core cross-border remittance transaction
-- ============================================================================

DEFINE TABLE remittance SCHEMAFULL
  PERMISSIONS
    FOR select WHERE merchantId = $auth.merchantId OR $scope = 'service'
    FOR create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                  ON remittance TYPE string;
DEFINE FIELD merchantId          ON remittance TYPE string
  ASSERT $value != NONE;
DEFINE FIELD corridorId          ON remittance TYPE string
  COMMENT 'References corridor_config.id (e.g. GBP_PKR, AED_BDT)';
DEFINE FIELD senderId            ON remittance TYPE string
  COMMENT 'Sender identity reference';
DEFINE FIELD beneficiaryId       ON remittance TYPE string
  COMMENT 'References beneficiary_kyc.id';
DEFINE FIELD sourceAmount        ON remittance TYPE decimal
  ASSERT $value > 0;
DEFINE FIELD sourceCurrency      ON remittance TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD destinationAmount   ON remittance TYPE decimal
  ASSERT $value > 0;
DEFINE FIELD destinationCurrency ON remittance TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD fxQuoteId           ON remittance TYPE string
  COMMENT 'References fx_quote.id — locked rate used for this transfer';
DEFINE FIELD exchangeRate        ON remittance TYPE decimal
  ASSERT $value > 0;
DEFINE FIELD fee                 ON remittance TYPE decimal
  DEFAULT 0;
DEFINE FIELD deliveryMethod      ON remittance TYPE string
  ASSERT $value IN ['MOBILE_WALLET', 'BANK_DEPOSIT', 'CASH_PICKUP', 'AGENT_NETWORK']
  COMMENT 'How the beneficiary receives funds';
DEFINE FIELD status              ON remittance TYPE string
  ASSERT $value IN [
    'QUOTE_LOCKED', 'AML_PENDING', 'AML_PASSED', 'AML_FAILED',
    'KYC_PENDING', 'KYC_VERIFIED', 'KYC_FAILED',
    'SUBMITTED', 'PROCESSING', 'DELIVERED', 'FAILED',
    'RETURNED', 'UNDER_REVIEW', 'CANCELLED'
  ];
DEFINE FIELD amlCheckId          ON remittance TYPE option<string>
  COMMENT 'References aml_check.id';
DEFINE FIELD complianceRecordId  ON remittance TYPE option<string>
  COMMENT 'References compliance_record.id';
DEFINE FIELD traceId             ON remittance TYPE string;
DEFINE FIELD idempotencyKey      ON remittance TYPE string
  ASSERT $value != NONE;
DEFINE FIELD channelRef          ON remittance TYPE option<string>;
DEFINE FIELD purpose             ON remittance TYPE option<string>
  COMMENT 'Purpose of remittance (family support, education, medical, etc.)';
DEFINE FIELD failureReason       ON remittance TYPE option<string>;
DEFINE FIELD deliveredAt         ON remittance TYPE option<datetime>;
DEFINE FIELD metadata            ON remittance TYPE option<object>;
DEFINE FIELD createdAt           ON remittance TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt           ON remittance TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

-- Indexes
DEFINE INDEX idx_remittance_idempotency
  ON remittance FIELDS idempotencyKey UNIQUE;
DEFINE INDEX idx_remittance_merchant_status
  ON remittance FIELDS merchantId, status;
DEFINE INDEX idx_remittance_corridor
  ON remittance FIELDS corridorId, createdAt;
DEFINE INDEX idx_remittance_sender
  ON remittance FIELDS senderId;
DEFINE INDEX idx_remittance_beneficiary
  ON remittance FIELDS beneficiaryId;
DEFINE INDEX idx_remittance_trace
  ON remittance FIELDS traceId;
DEFINE INDEX idx_remittance_created
  ON remittance FIELDS createdAt;

-- Event: State transition audit
DEFINE EVENT remittance_state_change ON TABLE remittance WHEN $before.status != $after.status THEN {
  CREATE event_store SET
    aggregateId   = $after.id,
    aggregateType = 'remittance',
    eventType     = string::concat('REMITTANCE_', $after.status),
    eventData     = {
      previousStatus:      $before.status,
      newStatus:           $after.status,
      sourceAmount:        $after.sourceAmount,
      sourceCurrency:      $after.sourceCurrency,
      destinationAmount:   $after.destinationAmount,
      destinationCurrency: $after.destinationCurrency,
      corridorId:          $after.corridorId,
      merchantId:          $after.merchantId,
      traceId:             $after.traceId
    },
    sequenceNumber = time::now(),
    createdAt      = time::now()
};

-- ============================================================================
-- TABLE: fx_quote
-- Foreign exchange quotes with time-limited validity
-- ============================================================================

DEFINE TABLE fx_quote SCHEMAFULL
  PERMISSIONS
    FOR select WHERE merchantId = $auth.merchantId OR $scope = 'service'
    FOR create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                ON fx_quote TYPE string;
DEFINE FIELD merchantId        ON fx_quote TYPE string
  ASSERT $value != NONE;
DEFINE FIELD corridorId        ON fx_quote TYPE string;
DEFINE FIELD sourceCurrency    ON fx_quote TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD destCurrency      ON fx_quote TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD midMarketRate     ON fx_quote TYPE decimal
  ASSERT $value > 0
  COMMENT 'Mid-market rate at time of quote';
DEFINE FIELD appliedRate       ON fx_quote TYPE decimal
  ASSERT $value > 0
  COMMENT 'Rate after markup applied';
DEFINE FIELD markupBps         ON fx_quote TYPE int
  DEFAULT 0
  COMMENT 'Markup in basis points';
DEFINE FIELD rateSource        ON fx_quote TYPE string
  COMMENT 'Provider of the underlying rate (e.g. reuters, open_exchange)';
DEFINE FIELD status            ON fx_quote TYPE string
  ASSERT $value IN ['ACTIVE', 'LOCKED', 'USED', 'EXPIRED', 'CANCELLED'];
DEFINE FIELD expiresAt         ON fx_quote TYPE datetime
  COMMENT 'Quote validity window — typically 30 minutes';
DEFINE FIELD lockedAt          ON fx_quote TYPE option<datetime>
  COMMENT 'Timestamp when merchant confirmed the quote';
DEFINE FIELD usedByRemittance  ON fx_quote TYPE option<string>
  COMMENT 'References remittance.id when consumed';
DEFINE FIELD createdAt         ON fx_quote TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt         ON fx_quote TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

DEFINE INDEX idx_fxquote_merchant
  ON fx_quote FIELDS merchantId, status;
DEFINE INDEX idx_fxquote_corridor
  ON fx_quote FIELDS corridorId, createdAt;
DEFINE INDEX idx_fxquote_expiry
  ON fx_quote FIELDS status, expiresAt
  COMMENT 'Used by expiry job to invalidate stale quotes';

-- Event: Quote lifecycle tracking
DEFINE EVENT fxquote_state_change ON TABLE fx_quote WHEN $before.status != $after.status THEN {
  CREATE event_store SET
    aggregateId   = $after.id,
    aggregateType = 'fx_quote',
    eventType     = string::concat('FX_QUOTE_', $after.status),
    eventData     = {
      previousStatus: $before.status,
      newStatus:      $after.status,
      corridorId:     $after.corridorId,
      appliedRate:    $after.appliedRate,
      merchantId:     $after.merchantId
    },
    sequenceNumber = time::now(),
    createdAt      = time::now()
};

-- ============================================================================
-- TABLE: aml_check
-- Anti-Money Laundering / Counter-Financing of Terrorism checks
-- ============================================================================

DEFINE TABLE aml_check SCHEMAFULL
  PERMISSIONS
    FOR select, create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                ON aml_check TYPE string;
DEFINE FIELD remittanceId      ON aml_check TYPE string
  COMMENT 'References remittance.id';
DEFINE FIELD senderId          ON aml_check TYPE string;
DEFINE FIELD beneficiaryId     ON aml_check TYPE string;
DEFINE FIELD screeningProvider ON aml_check TYPE string
  COMMENT 'e.g. dow_jones, refinitiv, comply_advantage';
DEFINE FIELD watchlistsChecked ON aml_check TYPE array<string>
  COMMENT 'OFAC, UN, EU, local PEP lists, etc.';
DEFINE FIELD status            ON aml_check TYPE string
  ASSERT $value IN ['PENDING', 'CLEAR', 'MATCH_FOUND', 'FALSE_POSITIVE', 'ESCALATED', 'BLOCKED'];
DEFINE FIELD riskScore         ON aml_check TYPE option<int>
  COMMENT 'Numeric risk score from screening provider (0-100)';
DEFINE FIELD matchDetails      ON aml_check TYPE option<object>
  COMMENT 'Details of any matches found';
DEFINE FIELD reviewedBy        ON aml_check TYPE option<string>
  COMMENT 'Compliance officer who reviewed (if escalated)';
DEFINE FIELD reviewNotes       ON aml_check TYPE option<string>;
DEFINE FIELD completedAt       ON aml_check TYPE option<datetime>;
DEFINE FIELD createdAt         ON aml_check TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt         ON aml_check TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

DEFINE INDEX idx_aml_remittance
  ON aml_check FIELDS remittanceId;
DEFINE INDEX idx_aml_sender
  ON aml_check FIELDS senderId, createdAt;
DEFINE INDEX idx_aml_status
  ON aml_check FIELDS status;

-- ============================================================================
-- TABLE: beneficiary_kyc
-- Know Your Customer records for remittance beneficiaries
-- ============================================================================

DEFINE TABLE beneficiary_kyc SCHEMAFULL
  PERMISSIONS
    FOR select WHERE merchantId = $auth.merchantId OR $scope = 'service'
    FOR create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                ON beneficiary_kyc TYPE string;
DEFINE FIELD merchantId        ON beneficiary_kyc TYPE string;
DEFINE FIELD fullName          ON beneficiary_kyc TYPE string;
DEFINE FIELD dateOfBirth       ON beneficiary_kyc TYPE option<string>
  COMMENT 'ISO 8601 date format';
DEFINE FIELD nationality       ON beneficiary_kyc TYPE option<string>
  COMMENT 'ISO 3166-1 alpha-2 country code';
DEFINE FIELD idDocumentType    ON beneficiary_kyc TYPE string
  ASSERT $value IN ['NATIONAL_ID', 'PASSPORT', 'DRIVING_LICENCE', 'VOTER_ID'];
DEFINE FIELD idDocumentNumber  ON beneficiary_kyc TYPE string;
DEFINE FIELD idDocumentCountry ON beneficiary_kyc TYPE string
  ASSERT $value IN ['PK', 'BD', 'NP', 'IQ'];
DEFINE FIELD kycProvider       ON beneficiary_kyc TYPE string
  COMMENT 'Primary KYC verification provider';
DEFINE FIELD kycStatus         ON beneficiary_kyc TYPE string
  ASSERT $value IN ['PENDING', 'VERIFIED', 'FAILED', 'EXPIRED', 'MANUAL_REVIEW'];
DEFINE FIELD riskRating        ON beneficiary_kyc TYPE string
  ASSERT $value IN ['LOW', 'MEDIUM', 'HIGH']
  DEFAULT 'MEDIUM';
DEFINE FIELD verifiedAt        ON beneficiary_kyc TYPE option<datetime>;
DEFINE FIELD expiresAt         ON beneficiary_kyc TYPE option<datetime>
  COMMENT 'KYC validity period — re-verification required after expiry';
DEFINE FIELD createdAt         ON beneficiary_kyc TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt         ON beneficiary_kyc TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

DEFINE INDEX idx_kyc_merchant
  ON beneficiary_kyc FIELDS merchantId;
DEFINE INDEX idx_kyc_document
  ON beneficiary_kyc FIELDS idDocumentNumber, idDocumentCountry;
DEFINE INDEX idx_kyc_status
  ON beneficiary_kyc FIELDS kycStatus, expiresAt;

-- ============================================================================
-- TABLE: corridor_config
-- Configuration per remittance corridor (source→destination pair)
-- ============================================================================

DEFINE TABLE corridor_config SCHEMAFULL
  PERMISSIONS
    FOR select WHERE $scope = 'service'
    FOR create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                  ON corridor_config TYPE string;
DEFINE FIELD sourceCurrency      ON corridor_config TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD destCurrency        ON corridor_config TYPE string
  ASSERT string::len($value) = 3;
DEFINE FIELD sourceCountry       ON corridor_config TYPE string;
DEFINE FIELD destCountry         ON corridor_config TYPE string
  ASSERT $value IN ['PK', 'BD', 'NP', 'IQ'];
DEFINE FIELD corridorCode        ON corridor_config TYPE string
  COMMENT 'e.g. GBP_PKR, AED_BDT, USD_NPR';
DEFINE FIELD active              ON corridor_config TYPE bool
  DEFAULT true;
DEFINE FIELD minAmount           ON corridor_config TYPE decimal
  ASSERT $value >= 0;
DEFINE FIELD maxAmount           ON corridor_config TYPE decimal
  ASSERT $value > 0;
DEFINE FIELD dailyLimit          ON corridor_config TYPE decimal
  COMMENT 'Per-sender daily limit in source currency';
DEFINE FIELD monthlyLimit        ON corridor_config TYPE decimal
  COMMENT 'Per-sender monthly limit in source currency';
DEFINE FIELD defaultMarkupBps    ON corridor_config TYPE int
  DEFAULT 50
  COMMENT 'Default FX markup in basis points';
DEFINE FIELD deliveryMethods     ON corridor_config TYPE array<string>
  COMMENT 'Supported delivery methods for this corridor';
DEFINE FIELD requiresEDD         ON corridor_config TYPE bool
  DEFAULT false
  COMMENT 'Enhanced Due Diligence required for this corridor';
DEFINE FIELD rateProviders       ON corridor_config TYPE array<string>
  COMMENT 'Ordered list of FX rate sources for this corridor';
DEFINE FIELD quoteValidityMins   ON corridor_config TYPE int
  DEFAULT 30
  COMMENT 'How long an FX quote remains valid';
DEFINE FIELD regulatoryNotes     ON corridor_config TYPE option<string>
  COMMENT 'Corridor-specific regulatory requirements';
DEFINE FIELD createdAt           ON corridor_config TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt           ON corridor_config TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

DEFINE INDEX idx_corridor_code
  ON corridor_config FIELDS corridorCode UNIQUE;
DEFINE INDEX idx_corridor_dest
  ON corridor_config FIELDS destCountry, active;

-- ============================================================================
-- TABLE: compliance_record
-- Comprehensive compliance audit trail per remittance
-- ============================================================================

DEFINE TABLE compliance_record SCHEMAFULL
  PERMISSIONS
    FOR select, create, update WHERE $scope = 'service'
    FOR delete NONE;

DEFINE FIELD id                ON compliance_record TYPE string;
DEFINE FIELD remittanceId      ON compliance_record TYPE string
  COMMENT 'References remittance.id';
DEFINE FIELD amlCheckId        ON compliance_record TYPE string
  COMMENT 'References aml_check.id';
DEFINE FIELD kycVerificationId ON compliance_record TYPE option<string>
  COMMENT 'References beneficiary_kyc.id';
DEFINE FIELD overallStatus     ON compliance_record TYPE string
  ASSERT $value IN ['PENDING', 'APPROVED', 'REJECTED', 'ESCALATED'];
DEFINE FIELD riskLevel         ON compliance_record TYPE string
  ASSERT $value IN ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
DEFINE FIELD sanctionsCleared  ON compliance_record TYPE bool
  DEFAULT false;
DEFINE FIELD pepCleared        ON compliance_record TYPE bool
  DEFAULT false;
DEFINE FIELD eddRequired       ON compliance_record TYPE bool
  DEFAULT false;
DEFINE FIELD eddCompleted      ON compliance_record TYPE bool
  DEFAULT false;
DEFINE FIELD approvedBy        ON compliance_record TYPE option<string>;
DEFINE FIELD approvalNotes     ON compliance_record TYPE option<string>;
DEFINE FIELD regulatoryRef     ON compliance_record TYPE option<string>
  COMMENT 'Regulatory reporting reference number';
DEFINE FIELD completedAt       ON compliance_record TYPE option<datetime>;
DEFINE FIELD createdAt         ON compliance_record TYPE datetime
  DEFAULT time::now();
DEFINE FIELD updatedAt         ON compliance_record TYPE datetime
  DEFAULT time::now()
  VALUE time::now();

DEFINE INDEX idx_compliance_remittance
  ON compliance_record FIELDS remittanceId UNIQUE;
DEFINE INDEX idx_compliance_status
  ON compliance_record FIELDS overallStatus;
DEFINE INDEX idx_compliance_risk
  ON compliance_record FIELDS riskLevel, overallStatus;