make_post_sell/docs/crypto-payments-state-machine.md
Russell Ballestrini 3fedfeb849 Update state machine documentation to use dash-separated naming
- Convert all state names from underscore to dash format in markdown documentation
- Ensures consistency across all documentation formats (dot, svg, markdown)
- All payment states now use dashes: confirmed-complete, underpaid-refunded, etc.
- Maintains consistency with the source dot file which is the canonical reference

This completes the documentation naming convention standardization.
2025-10-04 13:02:07 -04:00

338 lines
14 KiB
Markdown

# 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
```mermaid
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**
```python
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()`:
```python
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**
```python
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.