Fix crypto payment refund system with proper account balance handling

- Fix AttributeError by changing payment.crypto_type to payment.coin_type
- Fix Decimal * float error by using Decimal("1e12") instead of 1e12
- Add account-specific balance checking using payment.account_index
- Add comprehensive logging for refund attempts and wallet balance checks
- Update execute_refund to accept payment object for account context
- Ensure refunds are sent from correct account that received the funds

This resolves GroupR's underpayment refund issue and improves the reliability
of the crypto payment rescue system.
This commit is contained in:
Russell Ballestrini 2025-09-24 20:28:59 -04:00
parent 88566599ce
commit 581f2b2244
2 changed files with 66 additions and 12 deletions

View file

@ -149,28 +149,75 @@ class PaymentRescue:
"reason": "Payment received after quote expiration",
}
def execute_refund(self, refund_details):
def execute_refund(self, refund_details, payment=None):
"""
Execute the actual refund transaction.
Args:
refund_details: dict with refund information
payment: CryptoPayment object (needed for account_index)
Returns:
dict with transaction details or raises exception
"""
import logging
logger = logging.getLogger(__name__)
refund_amount_xmr = refund_details["refund_amount"]
refund_amount_piconero = int(refund_amount_xmr * Decimal("1e12"))
logger.info(f"Attempting to refund payment {refund_details['payment_id']}")
logger.info(
f"Refund amount: {refund_amount_xmr} XMR ({refund_amount_piconero} piconero)"
)
logger.info(f"Refund address: {refund_details['refund_address']}")
logger.info(f"Refund reason: {refund_details['reason']}")
# Check wallet balance for the specific account before attempting refund
account_index = payment.account_index if payment else 0
logger.info(f"Checking balance for account {account_index}")
try:
# Create the refund transaction
tx_result = self.crypto_client.transfer(
destinations=[
balance_result = self.crypto_client._call(
"get_balance", {"account_index": account_index}
)
unlocked_balance = balance_result.get("unlocked_balance", 0)
total_balance = balance_result.get("balance", 0)
logger.info(
f"Account {account_index} balance - Total: {total_balance} piconero ({Decimal(total_balance) / Decimal('1e12')} XMR)"
)
logger.info(
f"Account {account_index} balance - Unlocked: {unlocked_balance} piconero ({Decimal(unlocked_balance) / Decimal('1e12')} XMR)"
)
if unlocked_balance < refund_amount_piconero:
logger.warning(
f"Insufficient unlocked balance in account {account_index}: need {refund_amount_piconero} but only have {unlocked_balance} unlocked"
)
except Exception as balance_error:
logger.warning(
f"Could not check wallet balance for account {account_index}: {balance_error}"
)
try:
# Create the refund transaction from the specific account that received the payment
transfer_params = {
"destinations": [
{
"address": refund_details["refund_address"],
"amount": int(
refund_details["refund_amount"] * 1e12
), # Convert to piconero
"amount": refund_amount_piconero,
}
]
)
],
"account_index": account_index, # Specify the account that received the funds
"get_tx_key": True,
"do_not_relay": False,
"priority": 1, # Use low priority to reduce fees
}
logger.info(f"Calling transfer with params: {transfer_params}")
tx_result = self.crypto_client._call("transfer", transfer_params)
logger.info(f"Transfer successful: {tx_result}")
return {
"success": True,
@ -180,4 +227,7 @@ class PaymentRescue:
}
except Exception as e:
logger.error(
f"Refund failed for payment {refund_details['payment_id']}: {e}"
)
return {"success": False, "error": str(e), "refund_details": refund_details}

View file

@ -884,7 +884,9 @@ def process_payment(
logger.info(
f"Expired payment {crypto_payment.id} eligible for refund: {refund_details}"
)
result = payment_rescue.execute_refund(refund_details)
result = payment_rescue.execute_refund(
refund_details, crypto_payment
)
if result["success"]:
logger.info(
f"Refund executed for expired payment {crypto_payment.id}: TX {result['tx_hash']}"
@ -1083,7 +1085,7 @@ def process_payment(
logger.info(
f"Overpayment detected for {crypto_payment.id}: {refund_details}"
)
result = payment_rescue.execute_refund(refund_details)
result = payment_rescue.execute_refund(refund_details, crypto_payment)
if result["success"]:
logger.info(
f"Excess refunded for payment {crypto_payment.id}: TX {result['tx_hash']}"
@ -1145,7 +1147,9 @@ def process_payment(
logger.info(
f"Underpayment {crypto_payment.id} eligible for refund: {refund_details}"
)
result = payment_rescue.execute_refund(refund_details)
result = payment_rescue.execute_refund(
refund_details, crypto_payment
)
if result["success"]:
logger.info(
f"Refund executed for underpayment {crypto_payment.id}: TX {result['tx_hash']}"