Add refund address guards and fix sweep security
- Guard all refund processing with refund_address checks to avoid unnecessary loops - Replace dangerous sweep_all with exact amount transfers - Use transfer() with specific amounts instead of sweeping everything - Prevents stealing funds from other concurrent payments in same account - Only process refunds when customer has configured a refund address Performance: Skip expensive refund calculations when no refund address exists Security: Each payment only touches its own calculated amounts (91% + 9%)
This commit is contained in:
parent
326c79fbda
commit
663c0e9546
1 changed files with 29 additions and 12 deletions
|
|
@ -600,7 +600,7 @@ def process_payment(
|
|||
except Exception as e:
|
||||
logger.error(f"Failed to process full refund for expired payment {crypto_payment.id}: {e}")
|
||||
|
||||
elif payment_rescue:
|
||||
elif payment_rescue and crypto_payment.refund_address:
|
||||
# Not out of stock - do normal expired refund with restocking fee
|
||||
coin_config = get_coin_config(crypto_payment.coin_type)
|
||||
received_crypto = Decimal(total_recv) / coin_config["atomic_units"]
|
||||
|
|
@ -620,42 +620,57 @@ def process_payment(
|
|||
crypto_payment.refund_amount = int(refund_details["refund_amount"] * coin_config["atomic_units"])
|
||||
crypto_payment.refund_tx_hash = result["tx_hash"]
|
||||
crypto_payment.refund_reason = refund_details["reason"]
|
||||
crypto_payment.status = CryptoPayment.STATUS_EXPIRED_REFUNDED
|
||||
|
||||
# Now sweep the restocking fee to shop's wallet
|
||||
# Commit the refund first
|
||||
env_request.dbsession.add(crypto_payment)
|
||||
env_request.dbsession.flush()
|
||||
|
||||
# Now sweep the remaining restocking fee to shop's wallet (AFTER refund)
|
||||
fee_amount = int(refund_details["fee_amount"] * coin_config["atomic_units"])
|
||||
if fee_amount > 0 and crypto_payment.shop_sweep_to_address:
|
||||
try:
|
||||
# Small delay to ensure refund tx is processed
|
||||
import time
|
||||
time.sleep(2)
|
||||
|
||||
if crypto_payment.coin_type == "XMR":
|
||||
sweep_result = client._call("sweep_single", {
|
||||
# Send exact restocking fee amount (9%)
|
||||
sweep_result = client._call("transfer", {
|
||||
"account_index": crypto_payment.account_index,
|
||||
"address": crypto_payment.shop_sweep_to_address,
|
||||
"amount": fee_amount,
|
||||
"destinations": [{
|
||||
"address": crypto_payment.shop_sweep_to_address,
|
||||
"amount": fee_amount
|
||||
}],
|
||||
"priority": 1,
|
||||
"get_tx_hex": True,
|
||||
"do_not_relay": False,
|
||||
})
|
||||
fee_tx_hash = sweep_result.get("tx_hash_list", [None])[0]
|
||||
fee_tx_hash = sweep_result.get("tx_hash")
|
||||
actual_swept = fee_amount
|
||||
elif crypto_payment.coin_type == "DOGE":
|
||||
# Send exact restocking fee amount
|
||||
fee_crypto_amount = float(Decimal(fee_amount) / coin_config["atomic_units"])
|
||||
fee_tx_hash = client._call("sendtoaddress", [
|
||||
crypto_payment.shop_sweep_to_address,
|
||||
float(Decimal(fee_amount) / coin_config["atomic_units"])
|
||||
fee_crypto_amount
|
||||
])
|
||||
actual_swept = fee_amount
|
||||
else:
|
||||
fee_tx_hash = None
|
||||
actual_swept = 0
|
||||
|
||||
if fee_tx_hash:
|
||||
crypto_payment.swept_amount = fee_amount
|
||||
if fee_tx_hash and actual_swept > 0:
|
||||
crypto_payment.swept_amount = actual_swept
|
||||
crypto_payment.swept_tx_hash = fee_tx_hash
|
||||
crypto_payment.swept_timestamp = now_timestamp()
|
||||
logger.info(
|
||||
f"Restocking fee swept for payment {crypto_payment.id}: "
|
||||
f"{Decimal(fee_amount) / coin_config['atomic_units']} {crypto_payment.coin_type} "
|
||||
f"{Decimal(actual_swept) / coin_config['atomic_units']} {crypto_payment.coin_type} "
|
||||
f"TX: {fee_tx_hash}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to sweep restocking fee for payment {crypto_payment.id}: {e}")
|
||||
|
||||
crypto_payment.status = CryptoPayment.STATUS_EXPIRED_REFUNDED
|
||||
else:
|
||||
logger.error(
|
||||
f"Refund failed for expired payment {crypto_payment.id}: {result['error']}"
|
||||
|
|
@ -738,6 +753,7 @@ def process_payment(
|
|||
# Check for overpayment refund AFTER confirming the order (applies to both new and stuck payments)
|
||||
if (
|
||||
payment_rescue
|
||||
and crypto_payment.refund_address
|
||||
and crypto_payment.received_amount > crypto_payment.expected_amount
|
||||
and crypto_payment.invoice
|
||||
and crypto_payment.invoice.user
|
||||
|
|
@ -781,6 +797,7 @@ def process_payment(
|
|||
# Check for underpayment rescue when we have enough confirmations
|
||||
if (
|
||||
payment_rescue
|
||||
and crypto_payment.refund_address
|
||||
and min_confs >= int(crypto_payment.confirmations_required)
|
||||
and crypto_payment.invoice
|
||||
and crypto_payment.invoice.user
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue