diff --git a/docs/KOHA_STRIPE_SETUP.md b/docs/KOHA_STRIPE_SETUP.md
index cb33804e..61b028b9 100644
--- a/docs/KOHA_STRIPE_SETUP.md
+++ b/docs/KOHA_STRIPE_SETUP.md
@@ -9,11 +9,12 @@
## Overview
-The Koha donation system uses the existing Stripe account from `passport-consolidated` to process donations in NZD (New Zealand Dollars). This document guides you through setting up the required Stripe products and webhooks.
+The Koha donation system uses the existing Stripe account from `passport-consolidated` to process donations in multiple currencies. This document guides you through setting up the required Stripe products and webhooks.
**Account:** Same Stripe test account as passport-consolidated
-**Currency:** NZD (New Zealand Dollars)
+**Currencies:** 10 supported (NZD base + USD, EUR, GBP, AUD, CAD, JPY, CHF, SGD, HKD)
**Payment Types:** Recurring monthly subscriptions + one-time donations
+**Multi-Currency:** Uses Stripe's `currency_options` feature for automatic conversion
---
@@ -84,6 +85,71 @@ STRIPE_KOHA_50_PRICE_ID=price_
---
+## 2.5 Multi-Currency Setup with currency_options
+
+**IMPORTANT:** To support multiple currencies with a single price ID, configure `currency_options` for each monthly tier price.
+
+### Method 1: Stripe Dashboard (Recommended)
+
+1. Go to your product → Select a price (e.g., $5 NZD/month)
+2. Click **Add currency** in the pricing section
+3. Add each supported currency with converted amounts:
+
+| Base (NZD) | USD | EUR | GBP | AUD | CAD | JPY | CHF | SGD | HKD |
+|------------|------|------|------|------|------|------|------|------|------|
+| $5.00 | $3.00| €2.75| £2.35| $4.65| $4.10| ¥470 | 2.65 | $4.05| $23.40|
+| $15.00 | $9.00| €8.25| £7.05|$13.95|$12.30|¥1410 | 7.95 |$12.15| $70.20|
+| $50.00 |$30.00|€27.50|£23.50|$46.50|$41.00|¥4700 |26.50 |$40.50|$234.00|
+
+4. Repeat for all three monthly tier prices
+
+### Method 2: Stripe API
+
+Update existing prices via Stripe API:
+
+```bash
+stripe prices update price_YOUR_5_NZD_PRICE_ID \
+ --currency-options[usd][unit_amount]=300 \
+ --currency-options[eur][unit_amount]=275 \
+ --currency-options[gbp][unit_amount]=235 \
+ --currency-options[aud][unit_amount]=465 \
+ --currency-options[cad][unit_amount]=410 \
+ --currency-options[jpy][unit_amount]=470 \
+ --currency-options[chf][unit_amount]=265 \
+ --currency-options[sgd][unit_amount]=405 \
+ --currency-options[hkd][unit_amount]=2340
+```
+
+Repeat for $15 and $50 tier prices with their corresponding amounts.
+
+### Exchange Rate Calculation
+
+Our base currency is NZD. Exchange rates (as of 2025-10-08):
+
+```
+1 NZD = 0.60 USD = 0.55 EUR = 0.47 GBP = 0.93 AUD = 0.82 CAD
+ = 94.0 JPY = 0.53 CHF = 0.81 SGD = 4.68 HKD
+```
+
+**Note:** Rates are configurable in `src/config/currencies.config.js`. Update periodically or integrate live exchange rate API.
+
+### One-Time Donations (Dynamic Currency)
+
+For one-time donations, the code dynamically creates Stripe checkout sessions with the selected currency:
+
+```javascript
+sessionParams.line_items = [{
+ price_data: {
+ currency: currentCurrency.toLowerCase(), // e.g., 'usd', 'eur'
+ unit_amount: amount // Amount in cents/smallest currency unit
+ }
+}];
+```
+
+No additional configuration needed - Stripe handles any supported currency.
+
+---
+
## 3. Webhook Configuration
### Create Webhook Endpoint
@@ -223,11 +289,13 @@ FRONTEND_URL=http://localhost:9000
### Test API Endpoint
+**Test 1: Monthly donation in NZD**
```bash
curl -X POST http://localhost:9000/api/koha/checkout \
-H "Content-Type: application/json" \
-d '{
"amount": 1500,
+ "currency": "NZD",
"frequency": "monthly",
"tier": "15",
"donor": {
@@ -238,6 +306,24 @@ curl -X POST http://localhost:9000/api/koha/checkout \
}'
```
+**Test 2: One-time donation in USD**
+```bash
+curl -X POST http://localhost:9000/api/koha/checkout \
+ -H "Content-Type: application/json" \
+ -d '{
+ "amount": 2500,
+ "currency": "USD",
+ "frequency": "one_time",
+ "tier": "custom",
+ "donor": {
+ "name": "US Donor",
+ "email": "us@example.com"
+ },
+ "public_acknowledgement": true,
+ "public_name": "Anonymous US Supporter"
+ }'
+```
+
**Expected Response:**
```json
{
@@ -317,10 +403,12 @@ Enable detailed Stripe logs:
```bash
# In koha.service.js, logger.info statements will show:
-[KOHA] Creating checkout session: monthly donation of NZD $15.00
+[KOHA] Creating checkout session: monthly donation of USD $9.00 (NZD $15.00)
[KOHA] Checkout session created: cs_test_...
[KOHA] Processing webhook event: checkout.session.completed
-[KOHA] Donation recorded: NZD $15.00
+[KOHA] Checkout completed: monthly donation, tier: 15, currency: USD
+[KOHA] Donation recorded: USD $9.00 (NZD $15.00)
+[KOHA] Recurring donation recorded: EUR $8.25 (NZD $15.00)
```
---
diff --git a/public/js/components/currency-selector.js b/public/js/components/currency-selector.js
new file mode 100644
index 00000000..b5a897b9
--- /dev/null
+++ b/public/js/components/currency-selector.js
@@ -0,0 +1,85 @@
+/**
+ * Currency Selector Component
+ * Dropdown for selecting donation currency
+ */
+
+(function() {
+ 'use strict';
+
+ // Currency selector HTML
+ const selectorHTML = `
+
+
+ Select Currency
+
+
+ 🇳🇿 NZD - NZ Dollar
+ 🇺🇸 USD - US Dollar
+ 🇪🇺 EUR - Euro
+ 🇬🇧 GBP - British Pound
+ 🇦🇺 AUD - Australian Dollar
+ 🇨🇦 CAD - Canadian Dollar
+ 🇯🇵 JPY - Japanese Yen
+ 🇨🇭 CHF - Swiss Franc
+ 🇸🇬 SGD - Singapore Dollar
+ 🇭🇰 HKD - Hong Kong Dollar
+
+
+ Prices are automatically converted from NZD. Your selection is saved for future visits.
+
+
+ `;
+
+ // Initialize currency selector
+ function initCurrencySelector() {
+ // Find container (should have id="currency-selector-container")
+ const container = document.getElementById('currency-selector-container');
+ if (!container) {
+ console.warn('Currency selector container not found');
+ return;
+ }
+
+ // Insert selector HTML
+ container.innerHTML = selectorHTML;
+
+ // Get select element
+ const select = document.getElementById('currency-select');
+
+ // Set initial value from detected currency
+ const detectedCurrency = detectUserCurrency();
+ select.value = detectedCurrency;
+
+ // Trigger initial price update
+ if (typeof window.updatePricesForCurrency === 'function') {
+ window.updatePricesForCurrency(detectedCurrency);
+ }
+
+ // Listen for changes
+ select.addEventListener('change', function(e) {
+ const newCurrency = e.target.value;
+
+ // Save preference
+ saveCurrencyPreference(newCurrency);
+
+ // Update prices
+ if (typeof window.updatePricesForCurrency === 'function') {
+ window.updatePricesForCurrency(newCurrency);
+ }
+ });
+ }
+
+ // Auto-initialize when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initCurrencySelector);
+ } else {
+ initCurrencySelector();
+ }
+
+ // Expose init function globally
+ window.initCurrencySelector = initCurrencySelector;
+
+})();
diff --git a/public/js/components/footer.js b/public/js/components/footer.js
new file mode 100644
index 00000000..7baac78e
--- /dev/null
+++ b/public/js/components/footer.js
@@ -0,0 +1,95 @@
+/**
+ * Footer Component
+ * Shared footer for all Tractatus pages
+ */
+
+(function() {
+ 'use strict';
+
+ // Create footer HTML
+ const footerHTML = `
+
+
+
+
+
+
+
+
+
Tractatus Framework
+
+ Architectural constraints for AI safety that preserve human agency through structural, not aspirational, guarantees.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Te Tiriti o Waitangi: We acknowledge Te Tiriti o Waitangi and our commitment to partnership, protection, and participation. This project respects Māori data sovereignty (rangatiratanga) and collective guardianship (kaitiakitanga).
+
+
+
+
+
+
+ © ${new Date().getFullYear()} Tractatus AI Safety Framework. Licensed under Apache 2.0 .
+
+
+ Made in Aotearoa New Zealand 🇳🇿
+
+
+
+
+
+
+
+ `;
+
+ // Insert footer at end of body
+ if (document.body) {
+ document.body.insertAdjacentHTML('beforeend', footerHTML);
+ } else {
+ // If body not ready, wait for DOM
+ document.addEventListener('DOMContentLoaded', function() {
+ document.body.insertAdjacentHTML('beforeend', footerHTML);
+ });
+ }
+
+})();
diff --git a/public/js/utils/currency.js b/public/js/utils/currency.js
new file mode 100644
index 00000000..081b81f7
--- /dev/null
+++ b/public/js/utils/currency.js
@@ -0,0 +1,131 @@
+/**
+ * Currency Utilities (Client-Side)
+ * Multi-currency support for Koha donation form
+ */
+
+// Base prices in NZD (in cents)
+const BASE_PRICES_NZD = {
+ tier_5: 500, // $5 NZD
+ tier_15: 1500, // $15 NZD
+ tier_50: 5000 // $50 NZD
+};
+
+// Exchange rates: 1 NZD = X currency
+const EXCHANGE_RATES = {
+ NZD: 1.0,
+ USD: 0.60,
+ EUR: 0.55,
+ GBP: 0.47,
+ AUD: 0.93,
+ CAD: 0.82,
+ JPY: 94.0,
+ CHF: 0.53,
+ SGD: 0.81,
+ HKD: 4.68
+};
+
+// Currency metadata
+const CURRENCY_CONFIG = {
+ NZD: { symbol: '$', code: 'NZD', name: 'NZ Dollar', decimals: 2, flag: '🇳🇿' },
+ USD: { symbol: '$', code: 'USD', name: 'US Dollar', decimals: 2, flag: '🇺🇸' },
+ EUR: { symbol: '€', code: 'EUR', name: 'Euro', decimals: 2, flag: '🇪🇺' },
+ GBP: { symbol: '£', code: 'GBP', name: 'British Pound', decimals: 2, flag: '🇬🇧' },
+ AUD: { symbol: '$', code: 'AUD', name: 'Australian Dollar', decimals: 2, flag: '🇦🇺' },
+ CAD: { symbol: '$', code: 'CAD', name: 'Canadian Dollar', decimals: 2, flag: '🇨🇦' },
+ JPY: { symbol: '¥', code: 'JPY', name: 'Japanese Yen', decimals: 0, flag: '🇯🇵' },
+ CHF: { symbol: 'CHF', code: 'CHF', name: 'Swiss Franc', decimals: 2, flag: '🇨🇭' },
+ SGD: { symbol: '$', code: 'SGD', name: 'Singapore Dollar', decimals: 2, flag: '🇸🇬' },
+ HKD: { symbol: '$', code: 'HKD', name: 'Hong Kong Dollar', decimals: 2, flag: '🇭🇰' }
+};
+
+// Supported currencies
+const SUPPORTED_CURRENCIES = ['NZD', 'USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CHF', 'SGD', 'HKD'];
+
+/**
+ * Convert NZD amount to target currency
+ */
+function convertFromNZD(amountNZD, targetCurrency) {
+ const rate = EXCHANGE_RATES[targetCurrency];
+ return Math.round(amountNZD * rate);
+}
+
+/**
+ * Get tier prices for a currency
+ */
+function getTierPrices(currency) {
+ return {
+ tier_5: convertFromNZD(BASE_PRICES_NZD.tier_5, currency),
+ tier_15: convertFromNZD(BASE_PRICES_NZD.tier_15, currency),
+ tier_50: convertFromNZD(BASE_PRICES_NZD.tier_50, currency)
+ };
+}
+
+/**
+ * Format currency amount for display
+ */
+function formatCurrency(amountCents, currency) {
+ const config = CURRENCY_CONFIG[currency];
+ const amount = amountCents / 100;
+
+ // For currencies with symbols that should come after (none in our list currently)
+ // we could customize here, but Intl.NumberFormat handles it well
+
+ try {
+ return new Intl.NumberFormat('en-NZ', {
+ style: 'currency',
+ currency: currency,
+ minimumFractionDigits: config.decimals,
+ maximumFractionDigits: config.decimals
+ }).format(amount);
+ } catch (e) {
+ // Fallback if Intl fails
+ return `${config.symbol}${amount.toFixed(config.decimals)}`;
+ }
+}
+
+/**
+ * Get currency display name with flag
+ */
+function getCurrencyDisplayName(currency) {
+ const config = CURRENCY_CONFIG[currency];
+ return `${config.flag} ${config.code} - ${config.name}`;
+}
+
+/**
+ * Detect user's currency from browser/location
+ */
+function detectUserCurrency() {
+ // Try localStorage first
+ const saved = localStorage.getItem('tractatus_currency');
+ if (saved && SUPPORTED_CURRENCIES.includes(saved)) {
+ return saved;
+ }
+
+ // Try to detect from browser language
+ const lang = navigator.language || navigator.userLanguage || 'en-NZ';
+ const langMap = {
+ 'en-US': 'USD',
+ 'en-GB': 'GBP',
+ 'en-AU': 'AUD',
+ 'en-CA': 'CAD',
+ 'en-NZ': 'NZD',
+ 'ja': 'JPY',
+ 'ja-JP': 'JPY',
+ 'de': 'EUR',
+ 'de-DE': 'EUR',
+ 'fr': 'EUR',
+ 'fr-FR': 'EUR',
+ 'de-CH': 'CHF',
+ 'en-SG': 'SGD',
+ 'zh-HK': 'HKD'
+ };
+
+ return langMap[lang] || langMap[lang.substring(0, 2)] || 'NZD';
+}
+
+/**
+ * Save user's currency preference
+ */
+function saveCurrencyPreference(currency) {
+ localStorage.setItem('tractatus_currency', currency);
+}
diff --git a/public/koha.html b/public/koha.html
index f71d4f13..eabfa259 100644
--- a/public/koha.html
+++ b/public/koha.html
@@ -73,6 +73,9 @@
+
+
+
-
Why NZD (New Zealand Dollars)?
+
What currencies do you accept?
- The Tractatus Framework is developed in Aotearoa New Zealand. Accepting NZD simplifies our operations and reflects our commitment to local context and indigenous partnership.
+ We accept 10 major currencies: NZD, USD, EUR, GBP, AUD, CAD, JPY, CHF, SGD, and HKD. Prices are automatically converted from our base currency (NZD) using current exchange rates. All donations are tracked in both your chosen currency and NZD for transparency reporting.
@@ -315,13 +318,52 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Privacy Policy
+
Last updated: October 8, 2025
+
+
+
+
+
+ Privacy First: The Tractatus Framework is built on principles of human agency and transparency. We collect minimal data, never sell your information, and give you full control over your data.
+
+
+
+
+
+
+
+
+ 1. Information We Collect
+
+ 1.1 Information You Provide
+
+ Donations (Koha): Name (optional), email address (required for receipt), country (optional), payment information (processed by Stripe, not stored by us)
+ Media Inquiries: Name, email, organization, inquiry details
+ Case Submissions: Contact information, case description, supporting evidence
+ Account Creation (if applicable): Email, password (hashed), optional profile information
+
+
+ 1.2 Automatically Collected Information
+
+ Analytics: Page views, referring sites, browser type, device type, general location (country-level)
+ Cookies: Session management, preferences (e.g., selected currency), analytics
+ Server Logs: IP addresses, access times, pages accessed (retained for 90 days for security)
+
+
+ 1.3 Currency Selection
+
+ When you select a currency for donations, we may detect your approximate location to suggest an appropriate currency. This location data is:
+
+
+ Derived from your IP address (country-level only, not precise geolocation)
+ Used only to pre-select a currency in the donation form
+ Not stored permanently
+ Can be overridden by manual currency selection
+
+
+
+
+
+ 2. How We Use Your Information
+
+
+ Process Donations: Email receipts, acknowledge public supporters (opt-in only), maintain transparency dashboard
+ Respond to Inquiries: Answer media questions, review case submissions, provide support
+ Improve Services: Analyze usage patterns, fix bugs, enhance user experience
+ Security: Prevent fraud, detect abuse, protect against attacks
+ Legal Compliance: Comply with applicable laws, respond to legal requests
+ Communications: Send receipts, important updates (we never send marketing emails without explicit opt-in)
+
+
+
+
+
+ 3. Data Sharing and Disclosure
+
+ We Share Your Data With:
+
+ Stripe: Payment processing for donations (subject to Stripe's Privacy Policy )
+ MongoDB Atlas: Database hosting (subject to MongoDB's Privacy Policy )
+ Email Service Provider: For sending receipts and communications
+
+
+ We NEVER:
+
+ ❌ Sell your personal data
+ ❌ Share your data with advertisers
+ ❌ Use your data for tracking across other websites
+ ❌ Share donor information publicly without explicit opt-in
+
+
+ Legal Disclosures:
+
+ We may disclose your information if required by law, court order, or to protect our rights and safety. We will notify you of such requests unless prohibited by law.
+
+
+
+
+
+ 4. Data Retention
+
+
+ Donation Records: Retained indefinitely for transparency and tax purposes
+ Server Logs: Deleted after 90 days
+ Analytics Data: Aggregated, anonymized after 12 months
+ User Accounts: Retained until you request deletion
+ Inquiries/Submissions: Retained for 2 years, then archived or deleted
+
+
+
+
+
+ 5. Your Rights
+
+ You have the right to:
+
+
+ Access: Request a copy of your personal data
+ Correction: Update or correct inaccurate information
+ Deletion: Request deletion of your data (subject to legal obligations)
+ Portability: Receive your data in a machine-readable format
+ Opt-Out: Withdraw consent for public acknowledgements anytime
+ Object: Object to processing of your data
+
+
+
+ To exercise your rights, email: privacy@agenticgovernance.digital
+
+
+
+
+
+ 6. Cookies and Tracking
+
+ Essential Cookies: Required for site functionality (session management, authentication)
+
+ Preference Cookies: Remember your settings (currency selection, theme preferences)
+
+ Analytics Cookies: Privacy-respecting analytics (no cross-site tracking)
+
+
+ You can control cookies through your browser settings. Disabling cookies may affect site functionality.
+
+
+
+
+
+ 7. Security
+
+ We implement industry-standard security measures:
+
+
+ HTTPS encryption for all connections
+ Encrypted database storage
+ Password hashing (bcrypt)
+ Regular security audits
+ Access controls and monitoring
+ No storage of payment card data (handled by Stripe PCI-compliant systems)
+
+
+
+ While we take reasonable precautions, no system is 100% secure. Report security issues to: security@agenticgovernance.digital
+
+
+
+
+
+ 8. Children's Privacy
+
+
+ The Tractatus Framework is not directed at children under 13. We do not knowingly collect information from children. If you believe a child has provided us with personal data, please contact us at privacy@agenticgovernance.digital .
+
+
+
+
+
+ 9. International Data Transfers
+
+
+ The Tractatus Framework operates from New Zealand. If you access our services from other countries, your data may be transferred to and processed in New Zealand. By using our services, you consent to this transfer.
+
+
+
+ GDPR Compliance: For EU users, we comply with GDPR requirements including lawful basis for processing, data minimization, and your rights under Articles 15-22.
+
+
+
+
+
+ 10. Changes to This Policy
+
+
+ We may update this Privacy Policy from time to time. Changes will be posted on this page with an updated "Last updated" date. Material changes will be communicated via email (for users who provided email) or prominent notice on the website.
+
+
+
+
+
+ 11. Contact Us
+
+ For privacy-related questions or concerns:
+
+
+
+
+
+
+ Te Tiriti o Waitangi | Treaty Commitment
+
+ As a New Zealand-based project, we acknowledge Te Tiriti o Waitangi and our commitment to partnership, protection, and participation. Our privacy practices respect Māori concepts of data sovereignty (rangatiratanga) and collective guardianship (kaitiakitanga).
+
+
+
+
+
+
+
+
+
+
+