333 lines
14 KiB
Markdown
333 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 --> [*] : ✓ 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_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_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 ✅
|
|
```
|
|
Customer pays exact amount, gets product, invoice kept.
|
|
|
|
### **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
|
|
- `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,
|
|
],
|
|
# ... 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.
|