Fix crypto payment status display and USD conversion in payment history

STATUS DISPLAY IMPROVEMENTS:
- Set payment status to underpaid-refunded/expired-refunded immediately when refund is determined
- Status now reflects refund eligibility rather than waiting for refund execution
- Added confirmed-overpaid-refunded status mapping for UI display

PAYMENT HISTORY ENHANCEMENTS:
- Added USD conversion display for received amounts using locked exchange rate
- Fixed TypeError by properly handling float * Decimal multiplication in templates
- Added refund_tx_hash to payment data for refund status differentiation
- Show "Refund pending" vs "Refund completed" messages based on refund_tx_hash presence

USER EXPERIENCE IMPROVEMENTS:
- Clear messaging about 10-confirmation requirement for refunds
- Immediate status feedback when underpayment/expired payment is detected
- Proper distinction between refund eligibility and refund completion
- Enhanced refund status explanations with restocking fee information

This ensures users see accurate payment status immediately rather than confusing
"Payment Received" labels when their payment is actually being refunded.
This commit is contained in:
Russell Ballestrini 2025-09-24 21:21:38 -04:00
parent 49747420ff
commit e9c520b866
3 changed files with 32 additions and 9 deletions

View file

@ -884,6 +884,10 @@ def process_payment(
logger.info(
f"Expired payment {crypto_payment.id} eligible for refund: {refund_details}"
)
# Set status to expired-refunded immediately when refund is determined
crypto_payment.status = CryptoPayment.STATUS_EXPIRED_REFUNDED
result = payment_rescue.execute_refund(
refund_details, crypto_payment
)
@ -898,9 +902,7 @@ def process_payment(
)
crypto_payment.refund_tx_hash = result["tx_hash"]
crypto_payment.refund_reason = refund_details["reason"]
crypto_payment.status = (
CryptoPayment.STATUS_EXPIRED_REFUNDED
)
# Status already set above
# Commit the refund first
env_request.dbsession.add(crypto_payment)
@ -1147,6 +1149,10 @@ def process_payment(
logger.info(
f"Underpayment {crypto_payment.id} eligible for refund: {refund_details}"
)
# Set status to underpaid-refunded immediately when refund is determined
crypto_payment.status = CryptoPayment.STATUS_UNDERPAID_REFUNDED
result = payment_rescue.execute_refund(
refund_details, crypto_payment
)
@ -1154,11 +1160,12 @@ def process_payment(
logger.info(
f"Refund executed for underpayment {crypto_payment.id}: TX {result['tx_hash']}"
)
crypto_payment.status = CryptoPayment.STATUS_UNDERPAID_REFUNDED
# Status already set above
else:
logger.error(
f"Refund failed for underpayment {crypto_payment.id}: {result['error']}"
)
# Status remains underpaid-refunded, refund will be retried later
else:
# No refund possible - no refund address configured
logger.warning(

View file

@ -46,6 +46,8 @@
{% else %}
{{ '%.8f' % payment.received_crypto }} {{ payment.coin_type }}
{% endif %}
<br>
<small style="color: #6c757d;">${{ '%.2f' % (payment.received_crypto * (payment.rate_usd_per_coin|float)) }} USD</small>
{% if payment.confirmations > 0 %}
<br>
<small style="color: #6c757d;">{{ payment.confirmations }}/{{ payment.confirmations_required }} confirmations</small>
@ -88,11 +90,20 @@
{% elif payment.status.endswith('-refunded') %}
<div style="background: #d1ecf1; border: 1px solid #bee5eb; border-radius: 3px; padding: 8px; margin-top: 10px;">
<small>
💸 This payment was automatically refunded.
{% if payment.status == 'out-of-stock-refunded' %}
The item became unavailable after your payment - full refund issued.
{% if payment.refund_tx_hash %}
💸 This payment was automatically refunded.
{% if payment.status == 'out-of-stock-refunded' %}
The item became unavailable after your payment - full refund issued.
{% else %}
A 9% restocking fee was deducted to cover network costs.
{% endif %}
{% else %}
A 9% restocking fee was deducted to cover network costs.
⏳ Refund pending - waiting for your payment to reach 10 confirmations before processing the refund.
{% if payment.status == 'out-of-stock-refunded' %}
The item became unavailable after your payment - full refund will be issued.
{% else %}
A 9% restocking fee will be deducted to cover network costs.
{% endif %}
{% endif %}
</small>
</div>

View file

@ -799,7 +799,7 @@ def crypto_cancel(request):
# Clear the invoice reference BEFORE deleting the invoice (now that invoice_id is nullable)
crypto_payment.invoice_id = None
crypto_payment.invoice = None
delete_result = delete_invoice_by_id(request.dbsession, invoice.id)
# We don't fail the cancellation if invoice deletion fails
# The payment cancellation is more important than invoice cleanup
@ -879,6 +879,7 @@ def crypto_quotes_history(request):
"invoice_id": payment.invoice.id if payment.invoice else None,
"shop_name": payment.shop.name if payment.shop else "Unknown Shop",
"refund_reason": payment.refund_reason,
"refund_tx_hash": payment.refund_tx_hash,
}
payment_history.append(payment_data)
@ -955,6 +956,10 @@ def get_payment_status_info(status):
"expired": {"label": "Expired", "color": "#6c757d"},
"expired-refunded": {"label": "Expired - Refunded", "color": "#fd7e14"},
"underpaid-refunded": {"label": "Underpaid - Refunded", "color": "#fd7e14"},
"confirmed-overpaid-refunded": {
"label": "Overpaid - Refunded",
"color": "#fd7e14",
},
"cancelled": {"label": "Cancelled", "color": "#6c757d"},
"out-of-stock-refunded": {
"label": "Out of Stock - Refunded",