import logging
from decimal import Decimal
from huey import crontab
from huey . contrib . djhuey import periodic_task
from django . conf import settings
from django . core . mail import send_mail
from django . template . loader import render_to_string
from django . urls import reverse
from core . monero import AuctionWallet
from core . models import UserShippingAddress
from sales . models import ItemSale
logger = logging . getLogger ( ' django.server ' )
class EmailTemplate :
def __init__ ( self , item , scenario , role ) :
context = {
' sale ' : item ,
' site_name ' : settings . SITE_NAME ,
' site_url ' : settings . SITE_URL ,
' sale_path ' : reverse ( ' get_sale ' , args = [ item . id ] )
}
subject = render_to_string (
template_name = f ' sales/notify/ { scenario } / { role } /subject.txt ' ,
context = context ,
request = None
)
body = render_to_string (
template_name = f ' sales/notify/ { scenario } / { role } /body.txt ' ,
context = context ,
request = None
)
self . subject = ' ' . join ( subject . splitlines ( ) )
self . body = body
self . role = role
self . item = item
def send ( self ) :
if self . role == ' buyer ' :
to_address = self . item . bid . bidder . email
else :
to_address = self . item . item . owner . email
res = send_mail (
self . subject ,
self . body ,
settings . DEFAULT_FROM_EMAIL ,
[ to_address ]
)
return res
### Notifications
@periodic_task ( crontab ( minute = ' * ' ) )
def notify_buyer_of_pending_sale ( ) :
item_sales = ItemSale . objects . filter ( buyer_notified = False , sale_cancelled = False )
for sale in item_sales :
logger . info ( f ' [INFO] Sale # { sale . id } just created, notifying buyer. ' )
email_template = EmailTemplate (
item = sale ,
scenario = ' sale_created ' ,
role = ' buyer '
)
email_template . send ( )
sale . buyer_notified = True
sale . save ( )
@periodic_task ( crontab ( minute = ' * ' ) )
def notify_seller_of_funds_received ( ) :
item_sales = ItemSale . objects . filter ( seller_notified = False , buyer_notified = True ) . filter ( payment_received = True )
for sale in item_sales :
logger . info ( f ' [INFO] Funds received from buyer for sale # { sale . id } , notifying seller. ' )
email_template = EmailTemplate (
item = sale ,
scenario = ' funds_received ' ,
role = ' seller '
)
email_template . send ( )
sale . seller_notified = True
sale . save ( )
@periodic_task ( crontab ( minute = ' * ' ) )
def notify_buyer_of_shipment_confirmation ( ) :
item_sales = ItemSale . objects . filter ( item_shipped = True ) . filter ( buyer_notified_of_shipment = False )
for sale in item_sales :
logger . info ( f ' [INFO] Item shipped for sale # { sale . id } , notifying buyer. ' )
email_template = EmailTemplate (
item = sale ,
scenario = ' item_shipped ' ,
role = ' buyer '
)
email_template . send ( )
bidder_profile = UserShippingAddress . objects . get ( user = sale . bid . bidder )
bidder_profile . delete ( )
logger . info ( f ' [INFO] Buyer shipping info wiped for sale # { sale . id } ' )
sale . buyer_notified_of_shipment = True
sale . save ( )
### Payments
@periodic_task ( crontab ( minute = ' * ' ) )
def poll_for_buyer_escrow_payments ( ) :
aw = AuctionWallet ( )
if aw . connected is False :
logging . error ( ' Auction wallet is not connected. Quitting. ' )
return False
item_sales = ItemSale . objects . filter ( payment_received = False )
for sale in item_sales :
logger . info ( f ' [INFO] Polling escrow address # { sale . escrow_account_index } for sale # { sale . id } for new funds. ' )
sale_account = aw . wallet . accounts [ sale . escrow_account_index ]
balance = sale_account . balances ( ) [ 1 ]
sale . received_payment_xmr = balance
if balance > = Decimal ( sale . expected_payment_xmr ) :
logger . info ( f ' [INFO] Found payment of { sale . received_payment_xmr } XMR for sale # { sale . id } . ' )
sale . payment_received = True
sale . save ( )
@periodic_task ( crontab ( minute = ' */5 ' ) )
def pay_sellers_on_sold_items ( ) :
aw = AuctionWallet ( )
if aw . connected is False :
logging . error ( ' Auction wallet is not connected. Quitting. ' )
return False
item_sales = ItemSale . objects . filter ( item_received = True , payment_received = True ) . filter ( seller_paid = False )
for sale in item_sales :
logger . info ( f ' [INFO] Sending { sale . agreed_price_xmr } XMR from wallet account # { sale . escrow_account_index } to item owner \' s payout address for sale # { sale . id } . ' )
sale_account = aw . wallet . accounts [ sale . escrow_account_index ]
if sale_account . balances ( ) [ 1 ] > Decimal ( sale . agreed_price_xmr ) :
try :
aw . wallet . accounts [ sale . escrow_account_index ] . transfer (
sale . item . payout_address , sale . agreed_price_xmr
)
sale . seller_paid = True
sale . escrow_complete = True
sale . save ( )
except Exception as e :
logger . error ( f ' [ERROR] Unable to pay seller for sale # { sale . id } : ' )
else :
logger . warning ( f ' [WARNING] Not enough unlocked funds available in account # { sale . escrow_account_index } for sale # { sale . id } . ' )
if sale . seller_paid and sale . seller_notified_of_payout is False :
email_template = EmailTemplate (
item = sale ,
scenario = ' sale_completed ' ,
role = ' seller '
)
sent = email_template . send ( )
sale . seller_notified_of_payout = True
sale . save ( )
@periodic_task ( crontab ( minute = ' 0 ' , hour = ' * ' ) )
def pay_platform_on_sold_items ( ) :
aw = AuctionWallet ( )
if aw . connected is False :
logging . error ( ' Auction wallet is not connected. Quitting. ' )
return False
aof = settings . PLATFORM_WALLET_ADDRESS
if aof is None :
aof = str ( aw . wallet . accounts [ 0 ] . address ( ) )
item_sales = ItemSale . objects . filter ( escrow_complete = True , seller_paid = True , item_received = True ) . filter ( platform_paid = False )
for sale in item_sales :
logger . info ( f ' [INFO] Paying platform fees for sale # { sale . id } to wallet { aof } . ' )
sale_account = aw . wallet . accounts [ sale . escrow_account_index ]
if sale_account . balances ( ) [ 1 ] > = Decimal ( 0.0 ) :
try :
aw . wallet . accounts [ sale . escrow_account_index ] . sweep_all ( aof )
sale . platform_paid = True
sale . sale_finalized = True
sale . save ( )
return True
except Exception as e :
logger . error ( f ' [ERROR] Unable to pay platform for sale # { sale . id } - trying again ' )
else :
logger . warning ( f ' [WARNING] Not enough unlocked funds available in account # { sale . escrow_account_index } for sale # { sale . id } . ' )
@periodic_task ( crontab ( minute = ' 0 ' , hour = ' */12 ' ) )
def close_completed_items_sales ( ) :
item_sales = ItemSale . objects . filter ( platform_paid = True , sale_finalized = True )
for sale in item_sales :
logger . info ( f ' [INFO] Deleting item # { sale . item . id } and all accompanying bids, sales, meta, etc. ' )
sale . item . delete ( )
@periodic_task ( crontab ( minute = ' * ' ) )
def closed_cancelled_sales ( ) :
aw = AuctionWallet ( )
if aw . connected is False :
logging . error ( ' Auction wallet is not connected. Quitting. ' )
return False
item_sales = ItemSale . objects . filter ( sale_cancelled = True )
for sale in item_sales :
logger . info ( f ' [INFO] Deleting sale # { sale . id } and transferring back any sent funds to the buyer. ' )
sale_account = aw . wallet . accounts [ sale . escrow_account_index ]
if sale_account . balances ( ) [ 0 ] > Decimal ( 0.0 ) :
try :
sale_account . sweep_all ( sale . bid . return_address )
sale . delete ( )
except Exception as e :
logger . error ( f ' [ERROR] Unable to return funds to use for sale # { sale . id } . ' )
else :
sale . delete ( )