Handle delegated payment confirms and recover failed intents
This commit is contained in:
@@ -1988,9 +1988,11 @@ class PaymentContractCheckoutService:
|
|||||||
"payment": repaired.get("payment"),
|
"payment": repaired.get("payment"),
|
||||||
"subscription": repaired.get("subscription"),
|
"subscription": repaired.get("subscription"),
|
||||||
}
|
}
|
||||||
if intent.status in {"failed", "cancelled", "expired"}:
|
if intent.status in {"cancelled", "expired"}:
|
||||||
raise PaymentCheckoutError(409, f"intent status is {intent.status}")
|
raise PaymentCheckoutError(409, f"intent status is {intent.status}")
|
||||||
tx_hash_text = str(tx_hash or intent.tx_hash or "").strip().lower()
|
tx_hash_text = str(tx_hash or intent.tx_hash or "").strip().lower()
|
||||||
|
if intent.status == "failed" and not tx_hash_text:
|
||||||
|
raise PaymentCheckoutError(409, "intent status is failed and tx_hash is missing")
|
||||||
if not tx_hash_text:
|
if not tx_hash_text:
|
||||||
raise PaymentCheckoutError(400, "tx_hash required")
|
raise PaymentCheckoutError(400, "tx_hash required")
|
||||||
if not (tx_hash_text.startswith("0x") and len(tx_hash_text) == 66):
|
if not (tx_hash_text.startswith("0x") and len(tx_hash_text) == 66):
|
||||||
@@ -2028,7 +2030,20 @@ class PaymentContractCheckoutService:
|
|||||||
)
|
)
|
||||||
if not tx_to or not tx_from:
|
if not tx_to or not tx_from:
|
||||||
raise PaymentCheckoutError(409, "tx indexed partially; retry confirm")
|
raise PaymentCheckoutError(409, "tx indexed partially; retry confirm")
|
||||||
if tx_to != intent.receiver_address:
|
block_number = int(receipt.get("blockNumber") or 0)
|
||||||
|
latest_block = int(w3.eth.block_number)
|
||||||
|
confirmations = max(0, latest_block - block_number + 1) if block_number else 0
|
||||||
|
if confirmations < self.confirmations:
|
||||||
|
raise PaymentCheckoutError(
|
||||||
|
409, f"confirmations not enough: {confirmations}/{self.confirmations}"
|
||||||
|
)
|
||||||
|
event_match = self._extract_matching_event(receipt, intent)
|
||||||
|
event_payer = _normalize_address(event_match.get("payer")) if event_match else None
|
||||||
|
effective_payer = event_payer or tx_from
|
||||||
|
routed_via_delegate = bool(
|
||||||
|
event_match and tx_to and tx_to != intent.receiver_address
|
||||||
|
)
|
||||||
|
if tx_to != intent.receiver_address and not event_match:
|
||||||
self._mark_intent_failed(
|
self._mark_intent_failed(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
intent=intent,
|
intent=intent,
|
||||||
@@ -2045,29 +2060,24 @@ class PaymentContractCheckoutService:
|
|||||||
f"tx to mismatch: got={tx_to} expected={intent.receiver_address}",
|
f"tx to mismatch: got={tx_to} expected={intent.receiver_address}",
|
||||||
)
|
)
|
||||||
if intent.payment_mode == "strict" and intent.allowed_wallet:
|
if intent.payment_mode == "strict" and intent.allowed_wallet:
|
||||||
if tx_from != intent.allowed_wallet:
|
if effective_payer != intent.allowed_wallet:
|
||||||
self._mark_intent_failed(
|
self._mark_intent_failed(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
intent=intent,
|
intent=intent,
|
||||||
tx_hash=tx_hash_text,
|
tx_hash=tx_hash_text,
|
||||||
reason="sender_mismatch",
|
reason="sender_mismatch",
|
||||||
detail=f"tx sender mismatch: got={tx_from} expected={intent.allowed_wallet}",
|
detail=f"tx sender mismatch: got={effective_payer or tx_from} expected={intent.allowed_wallet}",
|
||||||
extra={"from_address": tx_from},
|
extra={
|
||||||
|
"from_address": tx_from,
|
||||||
|
"event_payer": event_payer,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
raise PaymentCheckoutError(
|
raise PaymentCheckoutError(
|
||||||
400,
|
400,
|
||||||
f"tx sender mismatch: got={tx_from} expected={intent.allowed_wallet}",
|
f"tx sender mismatch: got={effective_payer or tx_from} expected={intent.allowed_wallet}",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._require_user_wallet(user_id, tx_from)
|
self._require_user_wallet(user_id, effective_payer)
|
||||||
block_number = int(receipt.get("blockNumber") or 0)
|
|
||||||
latest_block = int(w3.eth.block_number)
|
|
||||||
confirmations = max(0, latest_block - block_number + 1) if block_number else 0
|
|
||||||
if confirmations < self.confirmations:
|
|
||||||
raise PaymentCheckoutError(
|
|
||||||
409, f"confirmations not enough: {confirmations}/{self.confirmations}"
|
|
||||||
)
|
|
||||||
event_match = self._extract_matching_event(receipt, intent)
|
|
||||||
if not event_match:
|
if not event_match:
|
||||||
self._mark_intent_failed(
|
self._mark_intent_failed(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@@ -2091,6 +2101,14 @@ class PaymentContractCheckoutService:
|
|||||||
redemption_meta["points_after"] = points_result.get("points_after")
|
redemption_meta["points_after"] = points_result.get("points_after")
|
||||||
redemption_meta["consumed_at"] = now_iso
|
redemption_meta["consumed_at"] = now_iso
|
||||||
confirmed_metadata["points_redemption"] = redemption_meta
|
confirmed_metadata["points_redemption"] = redemption_meta
|
||||||
|
if routed_via_delegate:
|
||||||
|
confirmed_metadata["tx_envelope"] = {
|
||||||
|
"outer_to": tx_to,
|
||||||
|
"outer_from": tx_from,
|
||||||
|
"event_payer": event_payer,
|
||||||
|
"receiver_expected": intent.receiver_address,
|
||||||
|
"matched_via_event": True,
|
||||||
|
}
|
||||||
self._rest(
|
self._rest(
|
||||||
"PATCH",
|
"PATCH",
|
||||||
"payment_intents",
|
"payment_intents",
|
||||||
@@ -2188,7 +2206,7 @@ class PaymentContractCheckoutService:
|
|||||||
"amount_units,payment_mode,allowed_wallet,order_id_hex,status,expires_at,tx_hash,metadata"
|
"amount_units,payment_mode,allowed_wallet,order_id_hex,status,expires_at,tx_hash,metadata"
|
||||||
),
|
),
|
||||||
"user_id": f"eq.{user_id}",
|
"user_id": f"eq.{user_id}",
|
||||||
"status": "in.(created,submitted,confirmed)",
|
"status": "in.(created,submitted,confirmed,failed)",
|
||||||
"order": "updated_at.desc",
|
"order": "updated_at.desc",
|
||||||
"limit": "5",
|
"limit": "5",
|
||||||
},
|
},
|
||||||
@@ -2205,9 +2223,15 @@ class PaymentContractCheckoutService:
|
|||||||
status = str(intent.status or "").strip().lower()
|
status = str(intent.status or "").strip().lower()
|
||||||
tx_hash_text = str(intent.tx_hash or "").strip().lower()
|
tx_hash_text = str(intent.tx_hash or "").strip().lower()
|
||||||
try:
|
try:
|
||||||
if status == "submitted" and tx_hash_text:
|
if status in {"submitted", "failed"} and tx_hash_text:
|
||||||
result = self.confirm_intent_tx(user_id, intent.intent_id, tx_hash_text)
|
result = self.confirm_intent_tx(user_id, intent.intent_id, tx_hash_text)
|
||||||
return {"ok": True, "action": "confirmed_submitted_intent", **result}
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"action": "confirmed_submitted_intent"
|
||||||
|
if status == "submitted"
|
||||||
|
else "recovered_failed_intent",
|
||||||
|
**result,
|
||||||
|
}
|
||||||
if status == "confirmed":
|
if status == "confirmed":
|
||||||
repaired = self._ensure_confirm_side_effects(user_id, intent, tx_hash_text)
|
repaired = self._ensure_confirm_side_effects(user_id, intent, tx_hash_text)
|
||||||
refreshed = self.get_intent(user_id, intent.intent_id)
|
refreshed = self.get_intent(user_id, intent.intent_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user