Fix payment priority order and refund processing
- Fix payment_priority() to include ALL refund statuses (EXPIRED_REFUNDED, UNDERPAID_REFUNDED, OUT_OF_STOCK_REFUNDED) not just DOUBLEPAY_REFUND - Auto-sweep operations (CONFIRMED, CONFIRMED_OVERPAID, NO_REFUND) now have lowest priority (3) - Refactor to use match/case statements for better readability - Add comprehensive docstring explaining priority system - Ensure refunds processed before auto-sweep for customer service priority Priority order: 0=refunds, 1=incoming, 2=other (pending/terminals), 3=auto-sweep
This commit is contained in:
parent
e04e91075b
commit
73bbf04fd4
5 changed files with 198 additions and 13 deletions
84
debug_stuck_refund.py
Normal file
84
debug_stuck_refund.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug script to manually process the stuck refund payment.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
sys.path.insert(0, '/home/fox/git/make_post_sell')
|
||||
|
||||
# Enable detailed logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger()
|
||||
|
||||
try:
|
||||
from pyramid.paster import get_appsettings
|
||||
from make_post_sell.lib.crypto_watcher import process_payment, get_crypto_client
|
||||
from make_post_sell.models.crypto_payment import CryptoPayment
|
||||
from make_post_sell.models import get_session_factory, get_engine
|
||||
from pyramid.testing import DummyRequest
|
||||
import transaction
|
||||
|
||||
# Get settings
|
||||
settings = get_appsettings('development.ini', name='main')
|
||||
|
||||
# Create session factory and get session
|
||||
session_factory = get_session_factory(get_engine(settings))
|
||||
dbsession = session_factory()
|
||||
|
||||
# Get the stuck payment
|
||||
payment_id = '9cee243c9df211f094ce9e7bc9aa750d'
|
||||
payment = dbsession.query(CryptoPayment).filter(CryptoPayment.id == payment_id).first()
|
||||
|
||||
if not payment:
|
||||
print(f"Payment {payment_id} not found!")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Found payment: {payment.id}")
|
||||
print(f"Status: {payment.status}")
|
||||
print(f"Received amount: {payment.received_amount}")
|
||||
print(f"Current confirmations: {payment.current_confirmations}")
|
||||
print(f"Required confirmations: {payment.confirmations_required}")
|
||||
print(f"Tx hashes: {payment.tx_hashes}")
|
||||
print(f"Refund address: {payment.refund_address}")
|
||||
|
||||
# Create mock request
|
||||
request = DummyRequest()
|
||||
request.dbsession = dbsession
|
||||
request.registry = type('MockRegistry', (), {'settings': settings})()
|
||||
request.tm = transaction.manager
|
||||
|
||||
# Get crypto client
|
||||
try:
|
||||
client = get_crypto_client(payment.coin_type)
|
||||
print(f"Got crypto client: {client}")
|
||||
except Exception as e:
|
||||
print(f"Failed to get crypto client: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Try to process the payment
|
||||
print("\n=== Attempting to process payment ===")
|
||||
try:
|
||||
# Process with empty incoming transfers to force confirmation update
|
||||
process_payment(request, payment, [], client=client)
|
||||
print("Payment processing completed successfully")
|
||||
except Exception as e:
|
||||
print(f"Error processing payment: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Check if payment status changed
|
||||
dbsession.refresh(payment)
|
||||
print(f"\nAfter processing:")
|
||||
print(f"Status: {payment.status}")
|
||||
print(f"Current confirmations: {payment.current_confirmations}")
|
||||
print(f"Refund tx hash: {payment.refund_tx_hash}")
|
||||
print(f"Refund amount: {payment.refund_amount}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Script error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if 'dbsession' in locals():
|
||||
dbsession.close()
|
||||
|
|
@ -3551,15 +3551,35 @@ def run_once(env, interval):
|
|||
# 1. Duplicate refunds (need wallet funds)
|
||||
# 2. Regular payments (might trigger auto-sweep which drains wallet)
|
||||
def payment_priority(payment):
|
||||
if payment.status == CryptoPayment.STATUS_DOUBLEPAY_REFUND:
|
||||
return 0 # Highest priority - process refunds first
|
||||
elif payment.status in [
|
||||
CryptoPayment.STATUS_RECEIVED,
|
||||
CryptoPayment.STATUS_CONFIRMED,
|
||||
]:
|
||||
return 1 # Lower priority - might trigger auto-sweep
|
||||
else:
|
||||
return 2 # Lowest priority - other statuses
|
||||
"""
|
||||
Payment processing priority since wallets are partitioned (no fund conflicts).
|
||||
0=refunds (customer service), 1=incoming, 2=other (pending, terminals), 3=auto-sweep.
|
||||
"""
|
||||
match payment.status:
|
||||
# Priority 0: Refunds - highest priority
|
||||
case (
|
||||
CryptoPayment.STATUS_DOUBLEPAY_REFUND
|
||||
| CryptoPayment.STATUS_EXPIRED_REFUNDED
|
||||
| CryptoPayment.STATUS_UNDERPAID_REFUNDED
|
||||
| CryptoPayment.STATUS_OUT_OF_STOCK_REFUNDED
|
||||
):
|
||||
return 0 # Process refunds first
|
||||
|
||||
# Priority 1: Incoming payments
|
||||
case CryptoPayment.STATUS_RECEIVED:
|
||||
return 1 # Process new payments
|
||||
|
||||
# Priority 3: Auto-sweep operations - lowest priority
|
||||
case (
|
||||
CryptoPayment.STATUS_CONFIRMED
|
||||
| CryptoPayment.STATUS_CONFIRMED_OVERPAID
|
||||
| CryptoPayment.STATUS_NO_REFUND
|
||||
):
|
||||
return 3 # Auto-sweep operations last
|
||||
|
||||
# Priority 2: All other statuses (must be last)
|
||||
case _:
|
||||
return 2 # Everything else in middle
|
||||
|
||||
sorted_payments = sorted(coin_payments, key=payment_priority)
|
||||
logger.info(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class StripeUserShop(RBase, Base):
|
|||
# Check if Stripe is configured for this shop
|
||||
if self.shop.stripe is None:
|
||||
return []
|
||||
|
||||
|
||||
return self.shop.stripe.Customer.list_payment_methods(
|
||||
self.stripe_customer, type="card"
|
||||
)["data"]
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def billing(request):
|
|||
if request.shop is None or request.shop.stripe is None:
|
||||
request.session.flash(("This shop doesn't accept credit cards yet.", "info"))
|
||||
return HTTPFound(get_referer_or_home(request))
|
||||
|
||||
|
||||
stripe_user_shop = request.shop.stripe_user_shop(request.user)
|
||||
|
||||
if stripe_user_shop is None:
|
||||
|
|
@ -55,7 +55,7 @@ def add_card(request):
|
|||
if request.shop.stripe is None:
|
||||
request.session.flash(("This shop doesn't accept credit cards yet.", "info"))
|
||||
return HTTPFound(get_referer_or_home(request))
|
||||
|
||||
|
||||
stripe_setup_intent = request.params.get("setup_intent")
|
||||
stripe_setup_intent_client_secret = request.params.get("setup_intent_client_secret")
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ def update_card(request):
|
|||
if request.shop.stripe is None:
|
||||
request.session.flash(("This shop doesn't accept credit cards yet.", "info"))
|
||||
return HTTPFound(get_referer_or_home(request))
|
||||
|
||||
|
||||
action = request.params.get("action", None)
|
||||
card_id = request.params.get("card_id")
|
||||
stripe_user_shop = request.shop.stripe_user_shop(request.user)
|
||||
|
|
|
|||
81
test_duplicate_detection.py
Normal file
81
test_duplicate_detection.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to manually check duplicate detection logic.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
# Add the project to Python path
|
||||
sys.path.insert(0, '/home/fox/git/make_post_sell')
|
||||
|
||||
from make_post_sell.lib.crypto_watcher import get_crypto_client
|
||||
|
||||
def test_wallet_transfers():
|
||||
"""Check what transfers the wallet can see for subaddress 4."""
|
||||
print("Testing wallet transfer visibility...")
|
||||
|
||||
try:
|
||||
client = get_crypto_client("XMR")
|
||||
print(f"Connected to XMR wallet: {client}")
|
||||
|
||||
# Check transfers for subaddress 4 specifically
|
||||
query_params = {
|
||||
"in": True,
|
||||
"out": False,
|
||||
"pending": True,
|
||||
"failed": False,
|
||||
"pool": True,
|
||||
"filter_by_height": False, # Get ALL transfers
|
||||
"subaddr_indices": [4],
|
||||
"account_index": 0,
|
||||
}
|
||||
|
||||
print(f"Querying wallet with params: {query_params}")
|
||||
result = client._call("get_transfers", query_params)
|
||||
|
||||
print(f"Raw wallet response: {json.dumps(result, indent=2)}")
|
||||
|
||||
# Process all transfer types
|
||||
all_transfers = []
|
||||
for transfer_type in ["in", "pending", "pool"]:
|
||||
if transfer_type in result:
|
||||
transfers = result[transfer_type]
|
||||
print(f"Found {len(transfers)} transfers in '{transfer_type}' category")
|
||||
all_transfers.extend(transfers)
|
||||
|
||||
for i, tx in enumerate(transfers):
|
||||
print(f" {transfer_type}[{i}]: height={tx.get('height', 'mempool')}, "
|
||||
f"amount={tx.get('amount', 0) / 1e12:.12f} XMR, "
|
||||
f"confirmations={tx.get('confirmations', 0)}, "
|
||||
f"txid={tx.get('txid', 'unknown')}")
|
||||
|
||||
print(f"Total transfers found: {len(all_transfers)}")
|
||||
|
||||
# Also check with height filter to see what the scanner would see
|
||||
scan_height = 3509810 # From your log
|
||||
query_with_filter = {
|
||||
"in": True,
|
||||
"out": False,
|
||||
"pending": True,
|
||||
"failed": False,
|
||||
"pool": True,
|
||||
"filter_by_height": True,
|
||||
"min_height": scan_height,
|
||||
}
|
||||
|
||||
print(f"\nNow checking with scanner filter (min_height={scan_height}):")
|
||||
print(f"Query params: {query_with_filter}")
|
||||
|
||||
filtered_result = client._call("get_transfers", query_with_filter)
|
||||
print(f"Filtered result: {json.dumps(filtered_result, indent=2)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error testing wallet: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_wallet_transfers()
|
||||
Loading…
Add table
Add a link
Reference in a new issue