-- Clavitor Corporate DB Schema -- 95% Paddle format + 5% our extensions -- ============================================ -- PADDLE PRODUCT CATALOG -- ============================================ CREATE TABLE products ( id TEXT PRIMARY KEY, -- pro_xxx (Paddle ID) name TEXT NOT NULL, -- "Personal", "Family", "Pro" description TEXT, tax_category TEXT, -- Paddle tax category image_url TEXT, custom_data TEXT, -- JSON for our use status TEXT NOT NULL DEFAULT 'active', -- active, archived created_at INTEGER NOT NULL, updated_at INTEGER ); CREATE TABLE prices ( id TEXT PRIMARY KEY, -- pri_xxx (Paddle ID) product_id TEXT NOT NULL REFERENCES products(id), description TEXT, -- Internal notes name TEXT, -- Shown to customers: "Yearly USD" billing_cycle TEXT, -- JSON: {"interval": "year", "frequency": 1} trial_period TEXT, -- JSON or null: {"interval": "day", "frequency": 7} tax_mode TEXT DEFAULT 'account_setting', unit_price TEXT NOT NULL, -- JSON: {"amount": "1200", "currency_code": "USD"} unit_price_overrides TEXT, -- JSON array for country-specific pricing quantity TEXT, -- JSON: {"minimum": 1, "maximum": null} status TEXT NOT NULL DEFAULT 'active', custom_data TEXT, -- JSON: our plan identifier (Personal/Pro/etc) created_at INTEGER NOT NULL, updated_at INTEGER ); CREATE INDEX idx_prices_product ON prices(product_id); CREATE TABLE discounts ( id TEXT PRIMARY KEY, -- dsc_xxx (Paddle ID) status TEXT NOT NULL DEFAULT 'active', description TEXT NOT NULL, -- "Johan MSP - 30% margin" enabled_for_checkout BOOLEAN DEFAULT 0, -- We apply manually, not customer-facing code TEXT, -- Null for MSP margins type TEXT NOT NULL, -- percentage, flat, flat_per_seat mode TEXT DEFAULT 'standard', amount TEXT NOT NULL, -- "30" for 30% currency_code TEXT, -- Only for flat types recur BOOLEAN DEFAULT 1, -- True for subscriptions maximum_recurring_intervals INTEGER, usage_limit INTEGER, restrict_to TEXT, -- JSON array of product/price IDs expires_at INTEGER, times_used INTEGER DEFAULT 0, discount_group_id TEXT, -- dsg_xxx for grouping custom_data TEXT, -- JSON: {"msp_id": "xxx", "tier": "standard"} created_at INTEGER NOT NULL, updated_at INTEGER ); -- ============================================ -- PADDLE CUSTOMERS + OUR HIERARCHY -- ============================================ CREATE TABLE customers ( id TEXT PRIMARY KEY, -- ctm_xxx (Paddle ID) -- PADDLE FIELDS (95%) email TEXT NOT NULL, name TEXT, locale TEXT DEFAULT 'en', status TEXT NOT NULL DEFAULT 'active', -- active, archived marketing_consent BOOLEAN DEFAULT 0, import_meta TEXT, -- JSON if imported -- OUR EXTENSIONS (5%) parent_id TEXT REFERENCES customers(id), -- Hierarchy: MSP → Customer → Dept level INTEGER NOT NULL DEFAULT 1, -- 1=MSP, 2=EndCustomer, 3=Dept internal_notes TEXT, custom_data TEXT, -- JSON: {"source": "web", "segment": "startup"} created_at INTEGER NOT NULL, updated_at INTEGER ); CREATE INDEX idx_customers_parent ON customers(parent_id); CREATE INDEX idx_customers_level ON customers(level); CREATE TABLE addresses ( id TEXT PRIMARY KEY, -- add_xxx (Paddle ID) customer_id TEXT NOT NULL REFERENCES customers(id), description TEXT, city TEXT, region TEXT, country_code TEXT NOT NULL, -- ISO 3166-1 alpha-2 zip_code TEXT, status TEXT NOT NULL DEFAULT 'active', created_at INTEGER NOT NULL, updated_at INTEGER ); CREATE INDEX idx_addresses_customer ON addresses(customer_id); CREATE TABLE businesses ( id TEXT PRIMARY KEY, -- biz_xxx (Paddle ID) customer_id TEXT NOT NULL REFERENCES customers(id), name TEXT NOT NULL, -- Company name company_number TEXT, tax_identifier TEXT, -- VAT number contacts TEXT, -- JSON: [{"name": "", "email": ""}] status TEXT NOT NULL DEFAULT 'active', custom_data TEXT, created_at INTEGER NOT NULL, updated_at INTEGER ); CREATE INDEX idx_businesses_customer ON businesses(customer_id); -- ============================================ -- PADDLE BILLING + OUR VAULT LINK -- ============================================ CREATE TABLE subscriptions ( id TEXT PRIMARY KEY, -- sub_xxx (Paddle ID) -- PADDLE REFERENCES customer_id TEXT NOT NULL REFERENCES customers(id), address_id TEXT NOT NULL REFERENCES addresses(id), business_id TEXT REFERENCES businesses(id), -- Optional for B2B -- PADDLE BILLING currency_code TEXT NOT NULL, status TEXT NOT NULL, -- active, past_due, paused, canceled collection_mode TEXT NOT NULL DEFAULT 'automatic', -- automatic, manual billing_details TEXT, -- JSON: payment_terms, etc. discount TEXT, -- JSON: {id, starts_at, ends_at, type} -- PADDLE TIMESTAMPS started_at INTEGER, first_billed_at INTEGER, next_billed_at INTEGER, paused_at INTEGER, canceled_at INTEGER, -- OUR EXTENSION (5%): Link to vault vault_l0 TEXT, -- Which vault this subscription pays for -- PADDLE METADATA billing_cycle TEXT, -- JSON: {interval, frequency} current_billing_period TEXT, -- JSON: {starts_at, ends_at} scheduled_change TEXT, -- JSON: {action, effective_at, resume_at} items TEXT NOT NULL, -- JSON array of subscription items management_urls TEXT, -- JSON: {update_payment_method, cancel} custom_data TEXT, import_meta TEXT, created_at INTEGER NOT NULL, updated_at INTEGER, consent_requirements TEXT -- JSON array ); CREATE INDEX idx_subscriptions_customer ON subscriptions(customer_id); CREATE INDEX idx_subscriptions_vault ON subscriptions(vault_l0); CREATE INDEX idx_subscriptions_status ON subscriptions(status); CREATE TABLE subscription_items ( id TEXT PRIMARY KEY, -- Paddle item ID subscription_id TEXT NOT NULL REFERENCES subscriptions(id), price_id TEXT NOT NULL, -- pri_xxx product_id TEXT NOT NULL, -- pro_xxx quantity INTEGER NOT NULL DEFAULT 1, status TEXT NOT NULL DEFAULT 'active', recurring BOOLEAN DEFAULT 1, created_at INTEGER NOT NULL, updated_at INTEGER, previously_billed_at INTEGER, next_billed_at INTEGER, trial_dates TEXT, -- JSON: {starts_at, ends_at} price_snapshot TEXT, -- JSON: price entity at time of add product_snapshot TEXT -- JSON: product entity at time of add ); CREATE INDEX idx_items_subscription ON subscription_items(subscription_id); CREATE TABLE transactions ( id TEXT PRIMARY KEY, -- txn_xxx (Paddle ID) subscription_id TEXT REFERENCES subscriptions(id), customer_id TEXT NOT NULL REFERENCES customers(id), address_id TEXT NOT NULL REFERENCES addresses(id), business_id TEXT REFERENCES businesses(id), invoice_id TEXT, -- Deprecated, but Paddle includes it invoice_number TEXT, discount_id TEXT REFERENCES discounts(id), -- TRANSACTION STATUS status TEXT NOT NULL, -- ready, billed, completed, past_due, canceled origin TEXT, -- checkout, api, subscription_renewal, etc. -- FINANCIALS (from Paddle 'details' object) currency_code TEXT NOT NULL, details TEXT NOT NULL, -- JSON: totals, tax_rates_used, line_items payments TEXT, -- JSON array of payment attempts -- ADJUSTED TOTALS (post-adjustments) adjusted_totals TEXT, payout_totals TEXT, adjusted_payout_totals TEXT, -- INVOICE billing_details TEXT, -- JSON: enable_checkout, payment_terms, etc. additional_information TEXT, -- Notes on invoice purchase_order_number TEXT, -- CHECKOUT (if applicable) checkout_url TEXT, -- TIMESTAMPS billed_at INTEGER, completed_at INTEGER, revised_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER, -- OUR EXTENSION: Track if we processed this processed_at INTEGER, processing_error TEXT ); CREATE INDEX idx_transactions_subscription ON transactions(subscription_id); CREATE INDEX idx_transactions_customer ON transactions(customer_id); CREATE INDEX idx_transactions_status ON transactions(status); CREATE INDEX idx_transactions_billed ON transactions(billed_at); CREATE TABLE adjustments ( id TEXT PRIMARY KEY, -- adj_xxx (Paddle ID) transaction_id TEXT NOT NULL REFERENCES transactions(id), subscription_id TEXT REFERENCES subscriptions(id), customer_id TEXT NOT NULL REFERENCES customers(id), action TEXT NOT NULL, -- chargeback, credit, refund type TEXT, -- partial, full, tax, proration amount TEXT NOT NULL, -- "1200" cents currency_code TEXT NOT NULL, totals TEXT NOT NULL, -- JSON: adjusted totals payout_totals TEXT, reason TEXT, credit_applied_to TEXT, -- For credit adjustments created_at INTEGER NOT NULL ); -- ============================================ -- OUR VAULT-SPECIFIC DATA (Beyond Paddle) -- ============================================ CREATE TABLE vaults ( l0 TEXT PRIMARY KEY, -- First 4 bytes of L3, hex customer_id TEXT NOT NULL REFERENCES customers(id), subscription_id TEXT REFERENCES subscriptions(id), status TEXT NOT NULL DEFAULT 'active', -- active, suspended, archived created_at INTEGER NOT NULL, last_seen_at INTEGER, -- For audit trail created_by_human INTEGER -- Who created this vault (if tracked) ); CREATE INDEX idx_vaults_customer ON vaults(customer_id); -- WL3 Registry (for POP sync) - OUR DATA CREATE TABLE credentials ( id INTEGER PRIMARY KEY, -- Our 8-byte int64 customer_id TEXT NOT NULL REFERENCES customers(id), p0 TEXT NOT NULL, -- First 4 bytes of PRF (lookup key) l0 TEXT NOT NULL REFERENCES vaults(l0), credential_id BLOB NOT NULL, -- WebAuthn credential ID wl3_path TEXT NOT NULL, -- Path in storage: a1/a1b2c3d4/20260405_abc.cla enrolled_by INTEGER, -- Who authorized this device created_at INTEGER NOT NULL, last_used_at INTEGER ); CREATE INDEX idx_credentials_customer ON credentials(customer_id); CREATE INDEX idx_credentials_l0 ON credentials(l0); CREATE INDEX idx_credentials_p0 ON credentials(p0); CREATE INDEX idx_credentials_created ON credentials(created_at); -- Our RBAC (not in Paddle - our internal access control) CREATE TABLE roles ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, -- SuperAdmin, MSPAdmin, TenantAdmin, VaultUser permissions TEXT NOT NULL, -- JSON description TEXT, created_at INTEGER NOT NULL ); -- Pre-populate INSERT INTO roles (id, name, permissions, description, created_at) VALUES (1, 'SuperAdmin', '{"all": true}', 'Full system access', strftime('%s', 'now')), (2, 'MSPAdmin', '{"customer_admin": true, "billing_view": true}', 'MSP-level admin', strftime('%s', 'now')), (3, 'TenantAdmin', '{"vault_create": true, "agent_create": true, "device_enroll": true}', 'Customer admin', strftime('%s', 'now')), (4, 'VaultUser', '{"vault_read": true, "vault_write": true}', 'Regular user', strftime('%s', 'now')), (5, 'BillingAdmin', '{"billing_view": true, "invoices": true}', 'Can view/pay invoices', strftime('%s', 'now')), (6, 'ReadOnly', '{"vault_read": true}', 'View only', strftime('%s', 'now')); -- Our internal users (humans in our system) CREATE TABLE humans ( id INTEGER PRIMARY KEY, -- Our 8-byte int64 customer_id TEXT REFERENCES customers(id), -- Can be null for our internal staff email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', created_at INTEGER NOT NULL, updated_at INTEGER ); -- RBAC: Who has what role for which customer/tenant CREATE TABLE role_assignments ( id INTEGER PRIMARY KEY, human_id INTEGER NOT NULL REFERENCES humans(id), customer_id TEXT NOT NULL REFERENCES customers(id), -- The tenant they have access to role_id INTEGER NOT NULL REFERENCES roles(id), granted_by INTEGER REFERENCES humans(id), created_at INTEGER NOT NULL, UNIQUE(human_id, customer_id, role_id) ); CREATE INDEX idx_role_assignments_human ON role_assignments(human_id); CREATE INDEX idx_role_assignments_customer ON role_assignments(customer_id); -- ============================================ -- POP SYNC STATE (Our distributed system) -- ============================================ CREATE TABLE pop_sync_state ( pop_id TEXT PRIMARY KEY, -- e.g., "zurich-01", "singapore-02" last_sync_at INTEGER NOT NULL, last_sync_cursor INTEGER, -- Last credential.id synced status TEXT NOT NULL DEFAULT 'active', -- active, paused, error error_message TEXT, updated_at INTEGER NOT NULL ); -- ============================================ -- AUDIT LOG (Our record keeping) -- ============================================ CREATE TABLE events ( id INTEGER PRIMARY KEY, occurred_at INTEGER NOT NULL, -- What happened event_type TEXT NOT NULL, -- customer_created, subscription_started, payment_received, etc. entity_type TEXT, -- customer, subscription, transaction, vault, etc. entity_id TEXT, -- The Paddle or internal ID -- Who did it / Who it affected customer_id TEXT REFERENCES customers(id), human_id INTEGER REFERENCES humans(id), vault_l0 TEXT, -- Details amount_cents INTEGER, currency_code TEXT, details TEXT, -- JSON: full event data -- Source source TEXT DEFAULT 'api', -- api, webhook, system ip_address TEXT, user_agent TEXT ); CREATE INDEX idx_events_time ON events(occurred_at); CREATE INDEX idx_events_customer ON events(customer_id); CREATE INDEX idx_events_type ON events(event_type); -- ============================================ -- SYNC TRACKING FIELDS (for dual-write layer) -- ============================================ -- Track when we last synced with Paddle and what event we processed -- This helps detect: "We haven't heard from Paddle in X minutes, retry" ALTER TABLE customers ADD COLUMN last_paddle_sync_at INTEGER; -- Timestamp of last successful sync ALTER TABLE customers ADD COLUMN paddle_event_id TEXT; -- Last webhook event ID processed ALTER TABLE customers ADD COLUMN pending_since INTEGER; -- When operation was queued for retry logic ALTER TABLE subscriptions ADD COLUMN last_paddle_sync_at INTEGER; ALTER TABLE subscriptions ADD COLUMN paddle_event_id TEXT; ALTER TABLE subscriptions ADD COLUMN pending_since INTEGER; ALTER TABLE discounts ADD COLUMN last_paddle_sync_at INTEGER; ALTER TABLE discounts ADD COLUMN paddle_event_id TEXT; ALTER TABLE discounts ADD COLUMN pending_since INTEGER; -- Index for finding stale pending syncs (retry after 5 minutes) CREATE INDEX idx_subscriptions_pending_sync ON subscriptions(sync_status, pending_since) WHERE sync_status = 'pending'; CREATE INDEX idx_customers_pending_sync ON customers(sync_status, pending_since) WHERE sync_status = 'pending'; -- Index for finding records that haven't synced in a while CREATE INDEX idx_subscriptions_stale_sync ON subscriptions(last_paddle_sync_at) WHERE last_paddle_sync_at IS NULL OR last_paddle_sync_at < strftime('%s', 'now', '-1 hour'); -- ============================================ -- WL3 STORAGE (for POP distribution) -- ============================================ -- TODO: Replace with WORM filesystem (256 shards) -- Path format: {shard}/{p0}/{timestamp}_{random}.cla CREATE TABLE wl3_storage ( path TEXT PRIMARY KEY, content BLOB NOT NULL, -- The encrypted wrapped L3 p0 TEXT NOT NULL, -- For quick lookup customer_id TEXT NOT NULL, created_at INTEGER NOT NULL, sync_count INTEGER DEFAULT 0, -- How many POPs have pulled this last_sync_at INTEGER ); CREATE INDEX idx_wl3_p0 ON wl3_storage(p0); CREATE INDEX idx_wl3_customer ON wl3_storage(customer_id); CREATE INDEX idx_wl3_created ON wl3_storage(created_at); -- TODO: SUPER DUPER SECURE POP AUTHENTICATION -- Requirements: -- 1. mTLS - POP presents certificate signed by our CA -- 2. Signed requests - each request signed with POP's private key -- 3. Replay protection - nonce + timestamp validation -- 4. IP allowlist - only known POP IPs -- 5. Rate limiting per POP ID -- 6. Request signing with HMAC -- 7. Audit logging of ALL POP access -- 8. Short-lived tokens (5 min) with refresh -- ============================================ -- CURRENCY CONVERSION & PRICING -- ============================================ -- -- DESIGN PRINCIPLE: "Pretty Commercial Prices" -- ------------------------------------------------- -- We NEVER show exact converted prices (e.g., $29.99 → €27.42). -- -- Why? Because: -- - €27.42 looks calculated, foreign, and suspicious -- - €29 looks local, intentional, and trustworthy -- - Psychological pricing works differently per market -- -- CONVERSION STRATEGY: -- 1. Convert USD amount using exchange rate -- 2. Round UP to "pretty" numbers based on magnitude: -- - Under €25: Round to nearest €0.99 (€23.21 → €24.00) -- - €25-€100: Round to nearest €5 (€47.32 → €49.99) -- - €100-€500: Round to nearest €10 (€123.45 → €129) -- - €500+: Round to nearest €50 or €99 ending -- 3. Never drop below margin floor (stored separately) -- 4. Use currency-local patterns (USD loves .99, JPY loves round 100s) -- -- This table tracks metadata needed for proper formatting. CREATE TABLE currencies ( code TEXT PRIMARY KEY, -- ISO 4217: "USD", "EUR", "JPY" name TEXT NOT NULL, -- "US Dollar", "Euro" decimals INTEGER NOT NULL DEFAULT 2, -- Minor units: USD=2, JPY=0, BHD=3 exchange_rate REAL, -- vs USD (1 USD = X of this currency). NULL = not fetched yet rate_fetched_at INTEGER, -- When rate was last updated symbol TEXT, -- "$", "€", "¥" symbol_position TEXT DEFAULT 'prefix', -- 'prefix' ($10) or 'suffix' (10€) pretty_pattern TEXT DEFAULT 'x.99', -- 'x.99', 'x00', 'x99', 'x50' is_active INTEGER DEFAULT 1, -- 1 = supported, 0 = disabled created_at INTEGER NOT NULL, updated_at INTEGER ); -- Exchange rate sources (in priority order): -- 1. Frankfurter API (free, ECB rates): https://api.frankfurter.app/latest?from=USD -- 2. Open Exchange Rates (requires API key): https://openexchangerates.org/ -- 3. Paddle's pricing-preview (for customer-facing prices with tax) -- -- NOTE: Exchange rates are fetched dynamically. Seed data below has NULL rates. -- Run: go run scripts/update_rates.go to populate exchange_rate column. -- Comprehensive ISO 4217 currency list (top 50 by trading volume) -- Rates should be fetched from Frankfurter API (free, no API key required) INSERT INTO currencies (code, name, decimals, exchange_rate, symbol, symbol_position, pretty_pattern, is_active, created_at) VALUES -- Major reserve currencies (always active) ('USD', 'US Dollar', 2, NULL, '$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('EUR', 'Euro', 2, NULL, '€', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('GBP', 'British Pound', 2, NULL, '£', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('JPY', 'Japanese Yen', 0, NULL, '¥', 'prefix', 'x00', 1, strftime('%s', 'now')), ('CNY', 'Chinese Yuan', 2, NULL, '¥', 'prefix', 'x.00', 1, strftime('%s', 'now')), -- Other major trading currencies ('AUD', 'Australian Dollar', 2, NULL, 'A$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('CAD', 'Canadian Dollar', 2, NULL, 'C$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('CHF', 'Swiss Franc', 2, NULL, 'Fr', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('SEK', 'Swedish Krona', 2, NULL, 'kr', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('NZD', 'New Zealand Dollar', 2, NULL, 'NZ$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('SGD', 'Singapore Dollar', 2, NULL, 'S$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('HKD', 'Hong Kong Dollar', 2, NULL, 'HK$', 'prefix', 'x.99', 1, strftime('%s', 'now')), ('NOK', 'Norwegian Krone', 2, NULL, 'kr', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('MXN', 'Mexican Peso', 2, NULL, '$', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('INR', 'Indian Rupee', 2, NULL, '₹', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('BRL', 'Brazilian Real', 2, NULL, 'R$', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('ZAR', 'South African Rand', 2, NULL, 'R', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('KRW', 'South Korean Won', 0, NULL, '₩', 'prefix', 'x000', 1, strftime('%s', 'now')), ('TWD', 'New Taiwan Dollar', 2, NULL, 'NT$', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('DKK', 'Danish Krone', 2, NULL, 'kr', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('PLN', 'Polish Zloty', 2, NULL, 'zł', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('THB', 'Thai Baht', 2, NULL, '฿', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('IDR', 'Indonesian Rupiah', 2, NULL, 'Rp', 'prefix', 'x000', 1, strftime('%s', 'now')), ('HUF', 'Hungarian Forint', 2, NULL, 'Ft', 'suffix', 'x00', 1, strftime('%s', 'now')), ('CZK', 'Czech Koruna', 2, NULL, 'Kč', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('ILS', 'Israeli Shekel', 2, NULL, '₪', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('CLP', 'Chilean Peso', 0, NULL, '$', 'prefix', 'x00', 1, strftime('%s', 'now')), ('PHP', 'Philippine Peso', 2, NULL, '₱', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('AED', 'UAE Dirham', 2, NULL, 'Dh', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('COP', 'Colombian Peso', 2, NULL, '$', 'prefix', 'x00', 1, strftime('%s', 'now')), ('SAR', 'Saudi Riyal', 2, NULL, '﷼', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('MYR', 'Malaysian Ringgit', 2, NULL, 'RM', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('RON', 'Romanian Leu', 2, NULL, 'lei', 'suffix', 'x.00', 1, strftime('%s', 'now')), -- Middle Eastern (3-decimal currencies) ('BHD', 'Bahraini Dinar', 3, NULL, 'BD', 'prefix', 'x.000', 0, strftime('%s', 'now')), ('KWD', 'Kuwaiti Dinar', 3, NULL, 'KD', 'prefix', 'x.000', 0, strftime('%s', 'now')), ('OMR', 'Omani Rial', 3, NULL, 'OR', 'prefix', 'x.000', 0, strftime('%s', 'now')), ('JOD', 'Jordanian Dinar', 3, NULL, 'JD', 'prefix', 'x.000', 0, strftime('%s', 'now')), -- Other European ('TRY', 'Turkish Lira', 2, NULL, '₺', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('RUB', 'Russian Ruble', 2, NULL, '₽', 'suffix', 'x.00', 0, strftime('%s', 'now')), -- Sanctions ('UAH', 'Ukrainian Hryvnia', 2, NULL, '₴', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('BGN', 'Bulgarian Lev', 2, NULL, 'лв', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('HRK', 'Croatian Kuna', 2, NULL, 'kn', 'suffix', 'x.00', 1, strftime('%s', 'now')), ('ISK', 'Icelandic Krona', 0, NULL, 'kr', 'suffix', 'x00', 1, strftime('%s', 'now')), -- African ('EGP', 'Egyptian Pound', 2, NULL, '£', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('NGN', 'Nigerian Naira', 2, NULL, '₦', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('KES', 'Kenyan Shilling', 2, NULL, 'KSh', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('GHS', 'Ghanaian Cedi', 2, NULL, '₵', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('MAD', 'Moroccan Dirham', 2, NULL, 'DH', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('TND', 'Tunisian Dinar', 3, NULL, 'DT', 'prefix', 'x.000', 0, strftime('%s', 'now')), -- Asian ('PKR', 'Pakistani Rupee', 2, NULL, '₨', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('BDT', 'Bangladeshi Taka', 2, NULL, '৳', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('VND', 'Vietnamese Dong', 0, NULL, '₫', 'suffix', 'x000', 1, strftime('%s', 'now')), ('MMK', 'Myanmar Kyat', 2, NULL, 'K', 'suffix', 'x00', 0, strftime('%s', 'now')), ('KHR', 'Cambodian Riel', 2, NULL, '៛', 'prefix', 'x00', 0, strftime('%s', 'now')), ('LAK', 'Lao Kip', 2, NULL, '₭', 'prefix', 'x000', 0, strftime('%s', 'now')), ('MNT', 'Mongolian Tugrik', 2, NULL, '₮', 'suffix', 'x00', 0, strftime('%s', 'now')), -- Latin American ('ARS', 'Argentine Peso', 2, NULL, '$', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('PEN', 'Peruvian Sol', 2, NULL, 'S/', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('UYU', 'Uruguayan Peso', 2, NULL, '$', 'prefix', 'x.00', 1, strftime('%s', 'now')), ('PYG', 'Paraguayan Guarani', 0, NULL, '₲', 'prefix', 'x000', 0, strftime('%s', 'now')), ('BOB', 'Bolivian Boliviano', 2, NULL, 'Bs', 'prefix', 'x.00', 0, strftime('%s', 'now')), -- Caribbean ('XCD', 'Eastern Caribbean Dollar', 2, NULL, '$', 'prefix', 'x.99', 0, strftime('%s', 'now')); -- Eastern Caribbean