Escrow hold pattern for marketplace transactions. Use when: marketplace P2P trades, holding buyer funds until seller delivers, protecting both parties.
from decimal import Decimal
from django.db import transaction
from apps.wallet.models import Wallet, Transaction
from apps.marketplace.models import EscrowHold
import uuid
@transaction.atomic
def create_escrow(
buyer_id: int,
seller_id: int,
amount: Decimal,
listing_id: int,
hold_days: int = 14,
) -> EscrowHold:
"""Hold buyer funds in escrow for a marketplace trade."""
if amount <= Decimal("0"):
raise ValueError("Escrow amount must be positive")
wallet = Wallet.objects.select_for_update().get(user_id=buyer_id)
if wallet.balance < amount:
raise ValueError("Insufficient balance for escrow")
# Debit buyer
wallet.balance -= amount
wallet.save(update_fields=["balance", "updated_at"])
ref = str(uuid.uuid4())
Transaction.objects.create(
wallet=wallet, amount=-amount,
transaction_type="escrow_hold",
reason=f"Escrow for listing #{listing_id}",
reference=ref, balance_after=wallet.balance,
)
from datetime import date, timedelta
return EscrowHold.objects.create(
buyer_id=buyer_id, seller_id=seller_id,
amount=amount, listing_id=listing_id,
reference=ref, status="held",
expires_at=date.today() + timedelta(days=hold_days),
)
from celery import shared_task
from datetime import date
@shared_task(name="marketplace.expire_escrows")
def expire_stale_escrows() -> int:
"""Refund escrows that have exceeded hold period."""
expired = EscrowHold.objects.filter(
status="held", expires_at__lt=date.today()
)
count = 0
for hold in expired:
refund_escrow(hold.pk, reason="Escrow expired")
count += 1
return count
| Bad | Why | Fix |
|---|---|---|
| Sending funds directly to seller | No buyer protection | Hold in escrow first |
| No expiry on escrow | Funds locked forever if seller ghosts | Auto-refund on expiry |
| Escrow without deducting buyer balance | Double-spending risk | Debit immediately |
& .\.venv\Scripts\python.exe -m ruff check . --fix
& .\.venv\Scripts\python.exe -m ruff format .
& .\.venv\Scripts\python.exe manage.py check --settings=app.settings_dev