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
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 requestslatepay_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 storageconfirmed_overpay_refunded_complete- Overpaid, customer got product + refundconfirmed_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 detectioncancelled- 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 progressout_of_stock_refunded- Out of stock refund in progress- Note:
latepay_refundedanddoublepay_refundedare 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 processedconfirmed_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 paymentslatepay_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_completestates- 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 storageconfirmed_overpay_refunded_complete- Overpaid + refundedconfirmed_overpay_not_refunded- Overpaid, no refund wallet
Failed Terminals (delete invoice):
expired- Never paidcancelled- 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
- Line 55:
-
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
statuscolumn containing string constants invoice_idnullable for proper invoice handlingcreated_timestampandupdated_timestampfor 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
- Invalid
received → expired: Check if payment was properly detected on blockchain - Priority conflicts: Verify processing order matches business requirements
- Invoice handling: Ensure successful payments keep invoices, failed payments delete them
- 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.