make_post_sell/docs/crypto-payments-state-machine.md
Russell Ballestrini a9b427e608 modified: docs/crypto-payments-state-machine.md
modified:   docs/state-machine.dot
	modified:   make_post_sell/lib/crypto_watcher/__init__.py
	modified:   make_post_sell/models/crypto_payment.py
	modified:   make_post_sell/tests/test_crypto_watcher.py
	modified:   make_post_sell/tests/test_double_spend_integration.py
	modified:   make_post_sell/tests/test_double_spend_protection.py
	modified:   make_post_sell/tests/test_invoice_deletion.py
	modified:   make_post_sell/tests/test_models.py
2025-10-01 19:02:39 -04:00

14 KiB

Crypto Payments State Machine

This document visualizes the complete state machine for cryptocurrency payments in the make-post-sell system, including semantic state groups, business logic rules, and implementation details.

State Machine Diagram

stateDiagram-v2
    [*] --> pending
    [*] --> doublepay_refunded : Duplicate payment detected
    [*] --> latepay_refunded: Late payment detected
    
    %% Main payment flow
    pending --> received : Payment detected in mempool
    pending --> expired : Payment timeout (never received)
    pending --> cancelled : User cancellation
    
    %% From received state - multiple possible outcomes
    %% NOTE: received payments CANNOT expire (detected in mempool, confirmations tracking)
    received --> confirmed : Sufficient payment + confirmations
    received --> confirmed_overpay : Overpayment detected
    received --> underpaid_refunded : Underpayment detected
    received --> out_of_stock_refunded : Product unavailable
    
    %% Successful payment paths
    confirmed --> confirmed_complete : Swept to cold storage
    confirmed_complete --> [*] : ✓ Terminal Success
    
    %% Overpayment refund flow
    confirmed_overpay --> confirmed_overpay_refunded : Initiate refund
    confirmed_overpay_refunded --> confirmed_overpay_refunded_complete : Refund confirmed
    confirmed_overpay_refunded --> confirmed_overpay_not_refunded : No refund wallet configured
    confirmed_overpay_refunded_complete --> [*] : ✓ Terminal Success
    confirmed_overpay_not_refunded --> [*] : ✓ Terminal Success (Not Refunded)
    
    %% Expired payment handling (terminal - late payments create new objects)
    expired --> [*] : ✓ Terminal Failed (Expired)
    
    %% Late payment objects (created separately for payments after expiration)
    latepay_refunded --> latepay_refunded_complete : Refund confirmed
    latepay_refunded --> latepay_not_refunded : No refund wallet configured
    latepay_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete)
    latepay_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded)
    
    %% Underpayment refund flow
    underpaid_refunded --> underpaid_refunded_complete : Refund confirmed
    underpaid_refunded --> underpaid_not_refunded : No refund wallet configured
    underpaid_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete)
    underpaid_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded)
    
    %% Out of stock refund flow
    out_of_stock_refunded --> out_of_stock_refunded_complete : Refund confirmed
    out_of_stock_refunded --> out_of_stock_not_refunded : No refund wallet configured
    out_of_stock_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete)
    out_of_stock_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded)
    
    %% Double payment refund flow
    doublepay_refunded --> doublepay_refunded_complete : Refund confirmed
    doublepay_refunded --> doublepay_not_refunded : No refund wallet configured
    doublepay_refunded_complete --> [*] : ✓ Terminal Failed (Refund Complete)
    doublepay_not_refunded --> [*] : ✓ Terminal Failed (Not Refunded)
    
    %% User cancellation (always terminal, only from pending)
    cancelled --> [*] : ✓ Terminal Failed (Cancelled)
    
    %% Style the states by semantic category
    classDef successState fill:#d4edda,stroke:#155724,color:#155724
    classDef initialWaitingState fill:#e7f3ff,stroke:#0056b3,color:#0056b3
    classDef refundState fill:#fff3cd,stroke:#856404,color:#856404
    classDef failedState fill:#f8d7da,stroke:#721c24,color:#721c24
    classDef processingState fill:#cce5ff,stroke:#004085,color:#004085
    
    %% Successful payments (customer received product)
    class confirmed,confirmed_complete,confirmed_overpay_refunded_complete,confirmed_overpay_not_refunded successState
    
    %% Initial/waiting states (entry points that don't come from 'received')
    class pending,latepay_refunded,doublepay_refunded initialWaitingState
    
    %% Active refund processing states
    class confirmed_overpay_refunded,underpaid_refunded,out_of_stock_refunded refundState
    
    %% Failed payments (customer did not receive product)
    class expired,cancelled,latepay_refunded_complete,latepay_not_refunded,underpaid_refunded_complete,underpaid_not_refunded,out_of_stock_refunded_complete,out_of_stock_not_refunded,doublepay_refunded_complete,doublepay_not_refunded failedState
    
    %% Processing states
    class received,confirmed_overpay processingState

Semantic State Groups

The state machine uses semantic groups to categorize states by business logic purpose. These groups determine invoice handling, priority processing, and system behavior.

🔵 Initial/Waiting States (Light Blue)

Entry point states that don't transition from received - they represent the start of payment flows:

  • pending - Initial state for new payment requests
  • latepay_refunded - Initial state for late payment objects (payments received after expiration)
  • doublepay_refunded - Initial state for duplicate payment objects (separate payment instances)

Business Logic: These states represent separate payment flows and are processed with Priority 0-2 depending on their nature.

🟢 Successful Payment States (Green)

Customer received their product - invoices are preserved:

  • confirmed - Normal successful payment (exact amount, confirmed)
  • confirmed_complete - Confirmed payment that has been swept to cold storage
  • confirmed_overpay_refunded_complete - Overpaid, customer got product + refund
  • confirmed_overpay_not_refunded - Overpaid, customer got product, no refund wallet configured

Business Logic: is_successful_payment() = True, should_keep_invoice() = True

🔴 Failed Payment States (Red)

Customer did not receive product - invoices are deleted:

  • expired - Payment window expired before any blockchain detection
  • cancelled - User cancelled payment (only from pending)
  • *_refunded_complete - Failed payments with completed refunds
  • *_not_refunded - Failed payments with no refund wallet configured

Business Logic: is_failed_payment() = True, should_keep_invoice() = False

🟡 Refund Processing States (Yellow)

Active refund workflows - intermediate states:

  • confirmed_overpay_refunded - Overpayment refund in progress (customer got product)
  • underpaid_refunded - Underpayment refund in progress
  • out_of_stock_refunded - Out of stock refund in progress
  • Note: latepay_refunded and doublepay_refunded are Initial/Waiting states, not regular refund processing

Business Logic: Priority 0 processing (highest), actively monitored for confirmation

🔵 Processing States (Blue)

Active payment processing states:

  • received - Payment detected on blockchain, being processed
  • confirmed_overpay - Overpayment confirmed, deciding refund action

Business Logic: Priority 1-3 processing, confirmation monitoring

Critical State Machine Rules

Rule 1: Blockchain Detection is Irreversible

  • INVALID: received → expired
  • VALID: pending → expired

Rationale: Once a payment is detected on the blockchain (received), it cannot "expire" - it already exists. Only payments that never arrive can expire.

Rule 2: Past-Tense Naming Convention

All status constants use past-tense naming for consistency:

  • STATUS_DOUBLEPAY_REFUNDED (correct)
  • STATUS_DOUBLEPAY_REFUND (incorrect - not past tense)

Rule 3: Entry Points vs Transitions

Some states are entry points for new payment objects, not transitions from existing payments:

  • pending - Entry point for new payments
  • latepay_refunded - Entry point for late payment objects (created after expiration)
  • doublepay_refunded - Entry point for duplicate payment objects

Rule 4: Invoice Preservation Logic

should_keep_invoice() = is_successful_payment()
  • Keep: Successful payments (customer got product)
  • Delete: Failed payments (customer did not get product)

Priority-Based Processing System

The crypto watcher processes payments by priority to ensure proper fund flow and customer service:

Priority 0 (Highest): Customer Refunds

  • doublepay_refunded, latepay_refunded, underpaid_refunded, out_of_stock_refunded
  • Rationale: Customer service is highest priority

Priority 1: New Incoming Payments

  • received
  • Rationale: Process new money quickly for customer experience

Priority 2: Other Processing

  • pending, monitoring states, intermediate states
  • Rationale: General processing tasks

Priority 3: Auto-Sweep to Shop Owner

  • confirmed, confirmed_overpay
  • Rationale: Move confirmed funds to shop owner

Priority 4 (Lowest): Restocking Fee Sweeps

  • *_refunded_complete states
  • Rationale: Most dangerous operation, requires high confirmations, done last

Business Logic Flows

Normal Payment Flow

pending → received → confirmed → confirmed_complete ✅

Customer pays exact amount, gets product, invoice kept, funds swept to cold storage.

Overpayment Flow

pending → received → confirmed_overpay → confirmed_overpay_refunded → confirmed_overpay_refunded_complete ✅

Customer overpays, gets product, gets refund, invoice kept.

Late Payment Flow

Original: pending → expired ❌
New object: latepay_refunded → latepay_refunded_complete ❌  

Original payment expires. Late payment creates new object, gets refunded, invoice deleted.

Underpayment Flow

pending → received → underpaid_refunded → underpaid_refunded_complete ❌

Customer pays too little, gets refund, no product, invoice deleted.

Duplicate Payment Flow

Original: pending → received → confirmed ✅
Duplicate: doublepay_refunded → doublepay_refunded_complete ❌

First payment succeeds, duplicate creates separate object and gets refunded.

Out of Stock Flow

pending → received → out_of_stock_refunded → out_of_stock_refunded_complete ❌

Product unavailable, customer gets refund, no product, invoice deleted.

Cancellation Flow

pending → cancelled ❌

User cancels before payment detected, invoice deleted.

Terminal States Analysis

Successful Terminals (keep invoice):

  • confirmed - Normal success (awaiting sweep)
  • confirmed_complete - Normal success + swept to cold storage
  • confirmed_overpay_refunded_complete - Overpaid + refunded
  • confirmed_overpay_not_refunded - Overpaid, no refund wallet

Failed Terminals (delete invoice):

  • expired - Never paid
  • cancelled - User cancelled
  • *_refunded_complete - Failed + refunded
  • *_not_refunded - Failed, no refund wallet

State Transition Validation

All transitions are validated via CryptoPayment.is_valid_transition():

VALID_TRANSITIONS = {
    STATUS_PENDING: [STATUS_RECEIVED, STATUS_EXPIRED, STATUS_CANCELLED],
    STATUS_RECEIVED: [
        STATUS_CONFIRMED,
        STATUS_CONFIRMED_OVERPAY,
        # NOTE: STATUS_EXPIRED removed - received payments cannot expire
        STATUS_UNDERPAID_REFUNDED,
        STATUS_DOUBLEPAY_REFUNDED,
        STATUS_OUT_OF_STOCK_REFUNDED,
    ],
    STATUS_CONFIRMED: [STATUS_CONFIRMED_COMPLETE],  # Can transition to complete after sweep
    STATUS_CONFIRMED_COMPLETE: [],  # Terminal - confirmed and swept
    # ... additional transitions
}

Property Testing: The complete test suite validates:

  • All valid transitions work correctly
  • Invalid transitions are properly rejected
  • 🔄 Priority system assignments are consistent
  • 📊 Graph analysis confirms no orphaned states or cycles
  • 🧪 Property-based testing validates invariants
  • 🛣️ All payment lifecycles reach valid terminal states

Implementation Details

Core Files

  • Model: make_post_sell/models/crypto_payment.py

    • Line 55: INITIAL_WAITING_STATUSES - New semantic group
    • Line 62: SUCCESSFUL_PAYMENT_STATUSES - Keep invoices
    • Line 70: FAILED_PAYMENT_STATUSES - Delete invoices
    • Line 386: VALID_TRANSITIONS - State transition rules
    • Line 488: is_successful_payment() - Business logic helper
    • Line 522: is_initial_waiting_state() - New helper method
  • Tests: make_post_sell/tests/test_crypto_payment_transitions.py

    • 21 comprehensive test methods
    • Property-based validation
    • Graph analysis and reachability testing
    • Priority system validation
  • Watcher: make_post_sell/lib/crypto_watcher.py

    • Priority-based processing engine
    • State transition enforcement
    • Invoice deletion logic
  • Views: make_post_sell/views/crypto.py

    • Status display logic
    • User-facing state information

Database Schema

  • Payments stored with status column containing string constants
  • invoice_id nullable for proper invoice handling
  • created_timestamp and updated_timestamp for audit trails

Testing Coverage

  • 155 total crypto payment tests all passing
  • Unit tests for individual state transitions
  • Integration tests for payment flows
  • Property-based tests for state machine invariants
  • Functional tests for end-to-end payment processing

Debugging and Monitoring

Log Messages

All state transitions generate structured logs:

Payment {id} state transition: {old_status} → {new_status} (context: {context})

Invalid Transition Handling

if not payment.is_valid_transition(new_status):
    logger.error(f"Invalid state transition for payment {payment.id}: {payment.status}{new_status}")
    return False

Common Issues

  1. Invalid received → expired: Check if payment was properly detected on blockchain
  2. Priority conflicts: Verify processing order matches business requirements
  3. Invoice handling: Ensure successful payments keep invoices, failed payments delete them
  4. Naming inconsistency: All status constants must use past-tense naming

This state machine provides a robust, well-tested foundation for cryptocurrency payment processing with clear semantic boundaries and validated state transitions.