19 changed files with 47443 additions and 24 deletions
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,74 @@
|
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||
cp app.py "app.py.auto-expire-pending.${STAMP}.bak" |
||||
|
||||
python3 <<'PY' |
||||
from pathlib import Path |
||||
|
||||
p = Path("app.py") |
||||
text = p.read_text() |
||||
|
||||
anchor = "def portal_invoice" |
||||
|
||||
if anchor not in text: |
||||
raise SystemExit("FAILED: portal_invoice route not found") |
||||
|
||||
# find insertion point (start of function body) |
||||
idx = text.index(anchor) |
||||
start = text.index(":", idx) + 1 |
||||
|
||||
inject_code = ''' |
||||
# === AUTO-EXPIRE STALE PENDING CRYPTO PAYMENTS === |
||||
try: |
||||
from datetime import datetime, timedelta |
||||
|
||||
conn2 = get_db_connection() |
||||
cur2 = conn2.cursor(dictionary=True) |
||||
|
||||
cur2.execute( |
||||
"SELECT id, payment_status, txid, created_at " |
||||
"FROM payments " |
||||
"WHERE invoice_id = %s AND payment_method = 'crypto' " |
||||
"ORDER BY id DESC LIMIT 1", |
||||
(invoice_id,) |
||||
) |
||||
last_payment = cur2.fetchone() |
||||
|
||||
if last_payment: |
||||
is_pending = str(last_payment.get("payment_status") or "").lower() == "pending" |
||||
has_tx = bool(last_payment.get("txid")) |
||||
|
||||
created_at = last_payment.get("created_at") |
||||
is_expired = False |
||||
|
||||
if created_at: |
||||
is_expired = datetime.utcnow() > (created_at + timedelta(minutes=15)) |
||||
|
||||
if is_pending and not has_tx and is_expired: |
||||
cur2.execute( |
||||
"UPDATE payments SET payment_status = 'expired' WHERE id = %s", |
||||
(last_payment["id"],) |
||||
) |
||||
conn2.commit() |
||||
print(f"[auto-expire] expired stale payment id={last_payment['id']} invoice_id={invoice_id}") |
||||
|
||||
conn2.close() |
||||
|
||||
except Exception as e: |
||||
print(f"[auto-expire] error: {e}") |
||||
# === END AUTO-EXPIRE === |
||||
''' |
||||
|
||||
text = text[:start] + inject_code + text[start:] |
||||
|
||||
p.write_text(text) |
||||
print("OK: auto-expire logic injected cleanly") |
||||
PY |
||||
|
||||
python3 -m py_compile app.py |
||||
echo "PY_COMPILE_OK" |
||||
|
||||
sudo systemctl restart otb_billing |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
@ -0,0 +1,44 @@
|
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||
cp app.py "app.py.auto-expire-pending-v2.${STAMP}.bak" |
||||
|
||||
python3 <<'PY' |
||||
from pathlib import Path |
||||
|
||||
p = Path("app.py") |
||||
text = p.read_text() |
||||
|
||||
old = """ cur2.execute( |
||||
"SELECT id, payment_status, txid, created_at " |
||||
"FROM payments " |
||||
"WHERE invoice_id = %s AND payment_method = 'crypto' " |
||||
"ORDER BY id DESC LIMIT 1", |
||||
(invoice_id,) |
||||
) |
||||
""" |
||||
|
||||
new = """ cur2.execute( |
||||
"SELECT id, payment_method, payment_currency, payment_status, txid, created_at " |
||||
"FROM payments " |
||||
"WHERE invoice_id = %s " |
||||
"AND UPPER(COALESCE(payment_currency,'')) IN ('ETHO','ETI','EGAZ','ETH','ARB') " |
||||
"ORDER BY id DESC LIMIT 1", |
||||
(invoice_id,) |
||||
) |
||||
""" |
||||
|
||||
if old not in text: |
||||
raise SystemExit("FAILED: old auto-expire query not found") |
||||
|
||||
text = text.replace(old, new, 1) |
||||
p.write_text(text) |
||||
print("OK: auto-expire query updated for real crypto rows") |
||||
PY |
||||
|
||||
python3 -m py_compile app.py |
||||
echo "PY_COMPILE_OK" |
||||
|
||||
sudo systemctl restart otb_billing |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
@ -0,0 +1,116 @@
|
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
cd /home/def/otb_billing/backend || exit 1 |
||||
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||
cp app.py "app.py.pending-payment-logic.${STAMP}.bak" |
||||
|
||||
python3 <<'PY' |
||||
from pathlib import Path |
||||
|
||||
p = Path("app.py") |
||||
text = p.read_text() |
||||
|
||||
old_block_1 = """ payment_id = (request.args.get("payment_id") or "").strip() |
||||
if not payment_id and pending_crypto_payment: |
||||
payment_id = str(pending_crypto_payment.get("id") or "").strip() |
||||
|
||||
if payment_id.isdigit(): |
||||
""" |
||||
|
||||
new_block_1 = """ payment_id = (request.args.get("payment_id") or "").strip() |
||||
if not payment_id and pending_crypto_payment: |
||||
stale_pending_without_tx = False |
||||
try: |
||||
created_dt = pending_crypto_payment.get("created_at") |
||||
if created_dt and created_dt.tzinfo is None: |
||||
created_dt = created_dt.replace(tzinfo=timezone.utc) |
||||
stale_pending_without_tx = ( |
||||
str(pending_crypto_payment.get("payment_status") or "").lower() == "pending" |
||||
and not pending_crypto_payment.get("txid") |
||||
and created_dt is not None |
||||
and datetime.now(timezone.utc) >= (created_dt + timedelta(minutes=2)) |
||||
) |
||||
except Exception: |
||||
stale_pending_without_tx = False |
||||
|
||||
if stale_pending_without_tx: |
||||
try: |
||||
cursor.execute(\"\"\" |
||||
UPDATE payments |
||||
SET payment_status = 'failed' |
||||
WHERE id = %s |
||||
AND payment_status = 'pending' |
||||
AND (txid IS NULL OR txid = '') |
||||
\"\"\", (pending_crypto_payment["id"],)) |
||||
conn.commit() |
||||
except Exception: |
||||
pass |
||||
pending_crypto_payment = None |
||||
else: |
||||
payment_id = str(pending_crypto_payment.get("id") or "").strip() |
||||
|
||||
if payment_id.isdigit(): |
||||
""" |
||||
|
||||
if old_block_1 not in text: |
||||
raise SystemExit("FAILED: block 1 not found") |
||||
|
||||
text = text.replace(old_block_1, new_block_1, 1) |
||||
|
||||
old_block_2 = """ else: |
||||
pending_crypto_payment["created_at_local"] = "" |
||||
pending_crypto_payment["lock_expires_at_local"] = "" |
||||
pending_crypto_payment["lock_expires_at_iso"] = "" |
||||
pending_crypto_payment["lock_expired"] = True |
||||
|
||||
received_dt = pending_crypto_payment.get("received_at") |
||||
""" |
||||
|
||||
new_block_2 = """ else: |
||||
pending_crypto_payment["created_at_local"] = "" |
||||
pending_crypto_payment["lock_expires_at_local"] = "" |
||||
pending_crypto_payment["lock_expires_at_iso"] = "" |
||||
pending_crypto_payment["lock_expired"] = True |
||||
|
||||
if ( |
||||
str(pending_crypto_payment.get("payment_status") or "").lower() == "pending" |
||||
and not pending_crypto_payment.get("txid") |
||||
and pending_crypto_payment.get("lock_expired") |
||||
): |
||||
try: |
||||
cursor.execute(\"\"\" |
||||
UPDATE payments |
||||
SET payment_status = 'failed' |
||||
WHERE id = %s |
||||
AND payment_status = 'pending' |
||||
AND (txid IS NULL OR txid = '') |
||||
\"\"\", (pending_crypto_payment["id"],)) |
||||
conn.commit() |
||||
except Exception: |
||||
pass |
||||
pending_crypto_payment = None |
||||
payment_id = "" |
||||
|
||||
received_dt = pending_crypto_payment.get("received_at") if pending_crypto_payment else None |
||||
""" |
||||
|
||||
if old_block_2 not in text: |
||||
raise SystemExit("FAILED: block 2 not found") |
||||
|
||||
text = text.replace(old_block_2, new_block_2, 1) |
||||
|
||||
p.write_text(text) |
||||
print("OK: pending payment logic patched") |
||||
PY |
||||
|
||||
python3 -m py_compile app.py |
||||
echo "PY_COMPILE_OK" |
||||
|
||||
sudo systemctl restart otb_billing |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
|
||||
echo |
||||
echo "===== verify patched area =====" |
||||
sed -n '5220,5325p' app.py |
||||
@ -0,0 +1,29 @@
|
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
python3 <<'PY' |
||||
from pathlib import Path |
||||
import re |
||||
|
||||
p = Path("app.py") |
||||
text = p.read_text() |
||||
|
||||
pattern = re.compile( |
||||
r"\n\s*# === KILL DEAD PENDING PAYMENT \(no txid \+ expired lock\) ===.*?" |
||||
r'print\("\[dead pending cleanup error\]", e\)\n', |
||||
re.DOTALL |
||||
) |
||||
|
||||
new_text, count = pattern.subn("\n", text, count=1) |
||||
if count == 0: |
||||
raise SystemExit("FAILED: broken dead-pending block not found") |
||||
|
||||
p.write_text(new_text) |
||||
print("OK: removed broken dead-pending block") |
||||
PY |
||||
|
||||
python3 -m py_compile app.py |
||||
echo "PY_COMPILE_OK" |
||||
|
||||
sudo systemctl restart otb_billing |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
@ -0,0 +1,26 @@
|
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
cd /home/def/otb_billing/backend || exit 1 |
||||
|
||||
GOOD_BAK="app.py.retry-email.20260327-023404.bak" |
||||
|
||||
echo "===== restoring known-good app.py =====" |
||||
cp "$GOOD_BAK" app.py |
||||
|
||||
echo |
||||
echo "===== compile check =====" |
||||
python3 -m py_compile app.py |
||||
echo "PY_COMPILE_OK" |
||||
|
||||
echo |
||||
echo "===== restart service =====" |
||||
sudo systemctl restart otb_billing |
||||
|
||||
echo |
||||
echo "===== service status =====" |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
|
||||
echo |
||||
echo "===== last 20 journal lines =====" |
||||
sudo journalctl -u otb_billing.service -n 20 --no-pager |
||||
@ -0,0 +1,49 @@
|
||||
cd /home/def/otb_billing/backend || exit 1 |
||||
set -e |
||||
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)" |
||||
cp app.py "app.py.fix-dead-pending.${STAMP}.bak" |
||||
|
||||
python3 <<'PY' |
||||
from pathlib import Path |
||||
|
||||
p = Path("app.py") |
||||
text = p.read_text() |
||||
|
||||
needle = "if payment_id.isdigit():" |
||||
|
||||
if needle not in text: |
||||
raise SystemExit("FAILED: anchor not found") |
||||
|
||||
inject = """ |
||||
# === KILL DEAD PENDING PAYMENT (no txid + expired lock) === |
||||
if pending_crypto_payment: |
||||
try: |
||||
txid = pending_crypto_payment.get("txid") |
||||
lock_expired = pending_crypto_payment.get("lock_expired") |
||||
|
||||
if (not txid) and lock_expired: |
||||
pending_crypto_payment = None |
||||
payment_id = "" |
||||
except Exception as e: |
||||
print("[dead pending cleanup error]", e) |
||||
""" |
||||
|
||||
text = text.replace(needle, inject + "\n" + needle, 1) |
||||
|
||||
p.write_text(text) |
||||
print("OK: dead pending cleanup injected") |
||||
PY |
||||
|
||||
echo "===== compile check =====" |
||||
python3 -m py_compile app.py && echo "PY_COMPILE_OK" |
||||
|
||||
echo "===== restart service =====" |
||||
sudo systemctl restart otb_billing |
||||
|
||||
echo "===== status =====" |
||||
sudo systemctl status otb_billing --no-pager -l |
||||
|
||||
echo |
||||
echo "DONE" |
||||
echo "Now refresh your invoice page" |
||||
@ -0,0 +1,15 @@
|
||||
.pay-selector { |
||||
background: #0f172a !important; |
||||
color: #e8eefc !important; |
||||
border: 1px solid rgba(255,255,255,0.2) !important; |
||||
} |
||||
|
||||
.pay-selector option { |
||||
background: #0f172a; |
||||
color: #e8eefc; |
||||
} |
||||
|
||||
.pay-selector option:checked { |
||||
background: #2563eb; |
||||
color: #ffffff; |
||||
} |
||||
Loading…
Reference in new issue