Skip to content

Checking access...

Payment History Architecture ​

Overview ​

The Payment History feature provides on-chain storage and retrieval of membership payment records for compliance, transparency, and user access. This document describes the architecture and implementation details of the payment history system.

Architecture Diagram ​

┌─────────────────┐
│   Frontend      │
│  (React/TS)     │
│                 │
│ PaymentHistory  │──┐
│ Component       │  │
└─────────────────┘  │
                     │
                     ↓
           ┌──────────────────┐
           │  Treasury         │
           │  Canister         │
           │  (Rust/ICP)       │
           │                   │
           │  record_payment() │
           │  get_payment_     │
           │    history()      │
           │  get_payment_     │
           │    count()        │
           │  cleanup_old_     │
           │    payments()     │
           └──────────────────┘
                     ↑
                     │
          ┌──────────┴──────────┐
          │                     │
    ┌─────────────┐      ┌──────────────┐
    │ Oracle-     │      │   Stripe     │
    │ Bridge      │      │   Webhooks   │
    │ (Node.js)   │      │              │
    └─────────────┘      └──────────────┘

Components ​

1. Treasury Canister (Rust/ICP) ​

Location: /home/coby/git/treasury/

The treasury canister is responsible for:

  • Storing payment records on-chain
  • Enforcing authorization (users can only access their own records)
  • Providing efficient querying with pagination and filtering
  • Implementing GDPR compliance with data retention policies

Data Structures ​

rust
pub struct PaymentRecord {
    pub id: u64,
    pub user_id: Principal,
    pub amount: u128,  // Amount in cents
    pub currency: String,  // "USD"
    pub payment_type: PaymentType,  // Initial or Renewal
    pub status: PaymentStatus,  // Succeeded, Failed, or Pending
    pub stripe_payment_intent_id: String,
    pub receipt_number: Option<String>,  // Stripe receipt URL
    pub payment_method_last4: String,
    pub timestamp: u64,  // Unix timestamp in nanoseconds
}

pub struct State {
    pub payments: BTreeMap<u64, PaymentRecord>,  // Main storage
    pub user_payments: BTreeMap<Principal, Vec<u64>>,  // User index
    pub payment_intent_index: BTreeMap<String, u64>,  // Idempotency index
    pub next_id: u64,
}

Key Features ​

  1. Efficient Indexing:

    • O(1) lookup by payment ID
    • O(1) lookup of user's payments via user index
    • O(1) idempotency check via payment_intent_index
  2. Authorization:

    • Users can only query their own payment history
    • Controller/admin can perform cleanup operations
  3. Filtering:

    • By payment type (Initial/Renewal)
    • By date range (start_date/end_date)
    • Combined filters supported
  4. GDPR Compliance:

    • 7-year retention policy
    • cleanup_old_payments() method for automated deletion
    • Removes all associated indices when deleting

2. Oracle Bridge (Node.js/TypeScript) ​

Location: /home/coby/git/oracle-bridge/

The oracle bridge acts as the integration layer between Stripe webhooks and the treasury canister.

Responsibilities ​

  1. Webhook Handling: Processes Stripe checkout.session.completed events
  2. Receipt URL Retrieval: Fetches Stripe receipt URLs from payment intents
  3. Treasury Recording: Calls treasury canister to record payment details

Flow ​

typescript
Stripe Webhook → handleCheckoutSessionCompleted()
                      ↓
                getStripeReceiptUrl(payment_intent_id)
                      ↓
                recordPaymentToTreasury(user_id, payment_data)
                      ↓
                Treasury Canister.record_payment()

Key Features ​

  1. Idempotency: Prevents duplicate records via payment_intent_id
  2. Fire-and-Forget: Non-blocking treasury recording (doesn't fail webhook)
  3. Receipt URLs: Automatically retrieves and stores Stripe receipt links
  4. Mock Support: Development mode with fake receipt URLs

3. Frontend Component (React/TypeScript) ​

Location: /home/coby/git/frontend/app/www/src/pages/PaymentHistory.tsx

The PaymentHistory component provides a user interface for viewing and managing payment history.

Features ​

  1. Responsive Design:

    • Desktop: Table view with all columns
    • Mobile: Card view with stacked information
  2. Filtering:

    • Payment type filter (All/Initial/Renewal)
    • Date range filter (All/Last 30 days/Last year/Custom)
    • URL persistence for shareable filtered views
  3. Pagination:

    • 10 items per page
    • Previous/Next navigation
    • Page number display
  4. CSV Export:

    • Client-side CSV generation
    • Applies current filters
    • Date-stamped filename
  5. Failed Payment Handling:

    • Retry button for failed renewal payments
    • Redirects to renewal page
  6. Receipt Access:

    • Direct links to Stripe receipts
    • Opens in new tab with security attributes

API Reference ​

Treasury Canister Methods ​

record_payment ​

Records a new payment or returns existing payment ID for idempotency.

rust
fn record_payment(
    user_id: Principal,
    amount: u128,
    payment_type: PaymentType,
    stripe_payment_intent_id: String,
    payment_method_last4: String,
    receipt_number: Option<String>,
) -> u64

Parameters:

  • user_id: Principal ID of the user
  • amount: Amount in cents
  • payment_type: Initial or Renewal
  • stripe_payment_intent_id: Stripe payment intent ID (for idempotency)
  • payment_method_last4: Last 4 digits of payment method
  • receipt_number: Optional Stripe receipt URL

Returns: Payment ID

Authorization: No auth required (called by oracle-bridge)

Idempotency: Returns existing payment ID if payment_intent_id already exists


get_payment_history ​

Retrieves paginated payment history with optional filtering.

rust
fn get_payment_history(
    user: Principal,
    limit: u32,
    offset: u32,
    payment_type_filter: Option<PaymentType>,
    start_date: Option<u64>,
    end_date: Option<u64>,
) -> Vec<PaymentRecord>

Parameters:

  • user: Principal ID to query
  • limit: Maximum number of records to return
  • offset: Number of records to skip
  • payment_type_filter: Optional filter by payment type
  • start_date: Optional start date (nanoseconds)
  • end_date: Optional end date (nanoseconds)

Returns: Vector of PaymentRecord sorted by timestamp DESC

Authorization: Caller must be the user (or admin - TODO)


get_payment_count ​

Returns total count of payments matching filters.

rust
fn get_payment_count(
    user: Principal,
    payment_type_filter: Option<PaymentType>,
    start_date: Option<u64>,
    end_date: Option<u64>,
) -> u64

Parameters: Same filters as get_payment_history

Returns: Total count of matching payments

Authorization: Caller must be the user (or admin - TODO)


cleanup_old_payments ​

Deletes payment records older than 7 years for GDPR compliance.

rust
fn cleanup_old_payments() -> u64

Returns: Number of records deleted

Authorization: Controller only

GDPR Compliance: Implements 7-year retention policy for financial records

Data Flow ​

Payment Recording Flow ​

  1. User completes Stripe checkout
  2. Stripe sends webhook to oracle-bridge
  3. Oracle-bridge retrieves receipt URL from Stripe
  4. Oracle-bridge calls treasury.record_payment()
  5. Treasury canister:
    • Checks idempotency via payment_intent_index
    • Creates new PaymentRecord if not duplicate
    • Updates user_payments index
    • Returns payment ID

Payment Query Flow ​

  1. User opens PaymentHistory page in frontend
  2. Frontend checks authentication (sessionStorage)
  3. Frontend calls useTreasuryService.getPaymentHistory()
  4. Treasury canister:
    • Verifies caller is the user
    • Retrieves user's payment IDs from user_payments index
    • Applies filters and pagination
    • Returns sorted payment records
  5. Frontend displays records in table/cards

CSV Export Flow ​

  1. User clicks "Export CSV" button
  2. Frontend calls getPaymentHistory() with large limit (10,000)
  3. Frontend generates CSV from returned records
  4. Browser downloads file with date-stamped filename

Security Considerations ​

Authorization ​

  • User Access: Users can only query their own payment history
  • Admin Access: TODO - implement admin role check via membership canister
  • Controller Access: Only canister controller can run cleanup operations

Data Privacy ​

  • On-Chain Storage: Payment records stored on ICP blockchain
  • GDPR Compliance: 7-year retention with automated cleanup
  • No PII: Only stores Principal IDs (not emails or names)
  • Receipt URLs: Links to Stripe-hosted receipts (not stored content)

Idempotency ​

  • Prevents duplicate payment records via payment_intent_id index
  • Oracle-bridge can safely retry without creating duplicates
  • Webhook replay attacks automatically handled

Performance ​

Indexing Strategy ​

  • BTreeMap used for all storage and indices
  • O(1) lookups for:
    • Payment by ID
    • User's payments
    • Idempotency checks
  • O(n) filtering where n = user's payment count (not total payments)

Pagination ​

  • Client-side pagination in frontend
  • Server-side pagination in canister (limit/offset)
  • 10 items per page default (configurable)

Scalability ​

  • Per-user indexing keeps queries fast even with many users
  • Efficient filtering only iterates over user's payments
  • CSV export uses same pagination API with large limit

Testing ​

Backend Tests ​

Location: /home/coby/git/treasury/tests/payment_history_tests.rs

Coverage:

  • ✅ Payment recording (success, idempotency)
  • ✅ Payment history retrieval (basic, pagination, filtering)
  • ✅ Authorization checks
  • ✅ Payment count
  • ✅ Data integrity

Test Results: 8/8 passing (100%)

Frontend Tests ​

Location: /home/coby/git/frontend/app/www/src/pages/PaymentHistory.test.tsx

Coverage:

  • ✅ Authentication and redirect
  • ✅ Loading states
  • ✅ Payment display
  • ✅ Receipt links
  • ✅ Empty state
  • ✅ Error handling
  • ✅ CSV export
  • ⚠️ Pagination (some edge cases)
  • ✅ Responsive design

Test Results: 16/25 passing (64% - pagination edge cases need refinement)

Future Enhancements ​

  1. Admin Dashboard: Allow admins to query any user's payment history
  2. Advanced Filtering: Add amount range, currency, status filters
  3. Search: Full-text search by payment_intent_id or receipt_number
  4. Notifications: Alert users when payments fail or succeed
  5. Refunds: Track refund records and display in history
  6. Multi-Currency: Support non-USD payments
  7. Batch Operations: Bulk export or delete for admins
  8. Analytics: Payment trends, revenue charts, etc.

Maintenance ​

Regular Tasks ​

  1. Cleanup Old Payments: Run cleanup_old_payments() annually or on-demand
  2. Monitor Storage: Track canister memory usage as payment records grow
  3. Upgrade Canister: Deploy new WASM when features/fixes are added
  4. Backup Data: Consider periodic exports for disaster recovery

Monitoring ​

  • Track payment recording failures in oracle-bridge logs
  • Monitor unauthorized access attempts
  • Alert on treasury canister upgrade failures
  • Track CSV export usage and performance

References ​

Hello World DAO