diff --git a/debug_stuck_refund.py b/debug_stuck_refund.py new file mode 100644 index 0000000..a29670e --- /dev/null +++ b/debug_stuck_refund.py @@ -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() \ No newline at end of file diff --git a/make_post_sell/lib/crypto_watcher.py b/make_post_sell/lib/crypto_watcher.py index 5458f50..5cc7108 100644 --- a/make_post_sell/lib/crypto_watcher.py +++ b/make_post_sell/lib/crypto_watcher.py @@ -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( diff --git a/make_post_sell/models/stripe_user_shop.py b/make_post_sell/models/stripe_user_shop.py index d32e544..7ff9826 100644 --- a/make_post_sell/models/stripe_user_shop.py +++ b/make_post_sell/models/stripe_user_shop.py @@ -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"] diff --git a/make_post_sell/views/billing.py b/make_post_sell/views/billing.py index e8d4ca3..fce5226 100644 --- a/make_post_sell/views/billing.py +++ b/make_post_sell/views/billing.py @@ -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) diff --git a/test_duplicate_detection.py b/test_duplicate_detection.py new file mode 100644 index 0000000..595b331 --- /dev/null +++ b/test_duplicate_detection.py @@ -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() \ No newline at end of file