refactor sale payment tasks

pull/3/head
lance 5 years ago
parent 003da201d5
commit 2c2ce8ab24

@ -3,6 +3,7 @@ from huey import crontab
from huey.contrib.djhuey import periodic_task from huey.contrib.djhuey import periodic_task
from sales.models import ItemSale from sales.models import ItemSale
logger = getLogger('django.server') logger = getLogger('django.server')
@periodic_task(crontab(minute='0', hour='*/12')) @periodic_task(crontab(minute='0', hour='*/12'))

@ -1,4 +1,4 @@
import logging from logging import getLogger
from huey import crontab from huey import crontab
from huey.contrib.djhuey import periodic_task from huey.contrib.djhuey import periodic_task
from core.helpers.email_template import EmailTemplate from core.helpers.email_template import EmailTemplate
@ -6,7 +6,7 @@ from core.models import UserShippingAddress
from sales.models import ItemSale from sales.models import ItemSale
logger = logging.getLogger('django.server') logger = getLogger('django.server')
@periodic_task(crontab(minute='*')) @periodic_task(crontab(minute='*'))
def notify_buyer_of_pending_sale(): def notify_buyer_of_pending_sale():
@ -52,3 +52,17 @@ def notify_buyer_of_shipment_confirmation():
logger.info(f'[INFO] Buyer shipping info wiped for sale #{sale.id}') logger.info(f'[INFO] Buyer shipping info wiped for sale #{sale.id}')
sale.buyer_notified_of_shipment = True sale.buyer_notified_of_shipment = True
sale.save() sale.save()
@periodic_task(crontab(minute='*'))
def notify_seller_of_sale_completed():
item_sales = ItemSale.objects.filter(seller_paid=True).filter(seller_notified_of_payout=False)
for sale in item_sales:
logger.info(f'[INFO] Sale completed for #{sale.id}, notifying seller along with payout details.')
email_template = EmailTemplate(
item=sale,
scenario='sale_completed',
role='seller'
)
email_template.send()
sale.seller_notified_of_payout = True
sale.save()

@ -4,44 +4,54 @@ from huey import crontab
from huey.contrib.djhuey import periodic_task from huey.contrib.djhuey import periodic_task
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from core.monero import AuctionWallet from core.monero import AuctionWallet, AuctionDaemon
from sales.models import ItemSale from sales.models import ItemSale
logger = logging.getLogger('django.server') logger = logging.getLogger('django.server')
@periodic_task(crontab(minute='*/3')) def connect_rpc(rpc_type):
def poll_for_buyer_escrow_payments(): if rpc_type == "daemon":
aw = AuctionWallet() rpc = AuctionDaemon()
if aw.connected is False: elif rpc_type == "wallet":
logging.error('[ERROR] Auction wallet is not connected. Quitting.') rpc = AuctionWallet()
else:
logger.error('[ERROR] Invalid RPC type specified. Use "daemon" or "wallet"')
return False
if rpc.connected is False:
logging.error(f'[ERROR] Auction {rpc_type} is not connected. Stopping task.')
return False return False
return rpc
@periodic_task(crontab(minute='*/2'))
def poll_for_buyer_escrow_payments():
wallet_rpc = connect_rpc("wallet")
item_sales = ItemSale.objects.filter(payment_received=False) item_sales = ItemSale.objects.filter(payment_received=False)
for sale in item_sales: for sale in item_sales:
logger.info(f'[INFO] Polling escrow address #{sale.escrow_account_index} for sale #{sale.id} for new funds.') 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] sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
unlocked = sale_account.balances()[1] tx_in = sale_account.incoming()
sale.received_payment_xmr = unlocked balances = sale_account.balances()
if unlocked >= Decimal(str(sale.expected_payment_xmr)): sale.received_payment_xmr = balances[0]
logger.info(f'[INFO] Found payment of {sale.received_payment_xmr} XMR for sale #{sale.id}.') if balances[0] >= Decimal(str(sale.expected_payment_xmr)) and tx_in:
sale.payment_received = True logger.info(f'[INFO] Found incoming transaction {tx_in[0].transaction} of {sale.received_payment_xmr} XMR for sale #{sale.id}.')
if tx_in[0].transaction.confirmations >= settings.BLOCK_CONFIRMATIONS_RCV:
logger.info(f'[INFO] The incoming transaction has {settings.BLOCK_CONFIRMATIONS_RCV} confirmations and enough funds. Marking payment received.')
sale.payment_received = True
else:
logger.info(f'[INFO] The incoming transaction only has {tx_in[0].transaction.confirmations} confirmations. Not enough to proceed.')
sale.save() sale.save()
@periodic_task(crontab(minute='*/3')) @periodic_task(crontab(minute='*/4'))
def pay_sellers_on_sold_items(): def pay_sellers_on_sold_items():
aw = AuctionWallet() wallet_rpc = connect_rpc("wallet")
if aw.connected is False:
logging.error('[ERROR] Auction wallet is not connected. Quitting.')
return False
item_sales = ItemSale.objects.filter(item_received=True, payment_received=True).filter(seller_paid=False) item_sales = ItemSale.objects.filter(item_received=True, payment_received=True).filter(seller_paid=False)
for sale in item_sales: for sale in item_sales:
# Take platform fees from the sale - the 50:50 split between buyer/seller # Take platform fees from the sale - the 50:50 split between buyer/seller
sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr
sale_account = aw.wallet.accounts[sale.escrow_account_index] sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
logger.info(f'[INFO] Seller needs to be paid for sale #{sale.id}. Found balances of {sale_account.balances()} in account #{sale.escrow_account_index}.')
if sale_account.balances()[1] >= Decimal(str(sale.agreed_price_xmr)): if sale_account.balances()[1] >= Decimal(str(sale.agreed_price_xmr)):
try: try:
# Construct a transaction so we can get current fee and subtract from the total # Construct a transaction so we can get current fee and subtract from the total
@ -64,31 +74,17 @@ def pay_sellers_on_sold_items():
else: else:
logger.warning(f'[WARNING] Not enough unlocked funds available in account #{sale.escrow_account_index} for sale #{sale.id}.') 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='*/30')) @periodic_task(crontab(minute='*/30'))
def pay_platform_on_sold_items(): def pay_platform_on_sold_items():
aw = AuctionWallet() wallet_rpc = connect_rpc("wallet")
if aw.connected is False:
logging.error('[ERROR] Auction wallet is not connected. Quitting.')
return False
aof = settings.PLATFORM_WALLET_ADDRESS aof = settings.PLATFORM_WALLET_ADDRESS
if aof is None: if aof is None:
aof = str(aw.wallet.accounts[0].address()) aof = str(wallet_rpc.wallet.accounts[0].address())
item_sales = ItemSale.objects.filter(escrow_complete=True, seller_paid=True, item_received=True).filter(platform_paid=False) item_sales = ItemSale.objects.filter(escrow_complete=True, seller_paid=True, item_received=True).filter(platform_paid=False)
for sale in item_sales: for sale in item_sales:
logger.info(f'[INFO] Paying platform fees for sale #{sale.id} to wallet {aof}.') logger.info(f'[INFO] Paying platform fees for sale #{sale.id} to wallet {aof}.')
sale_account = aw.wallet.accounts[sale.escrow_account_index] sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
bal = sale_account.balances()[1] bal = sale_account.balances()[1]
if bal >= 0: if bal >= 0:
try: try:
@ -114,16 +110,12 @@ def refund_buyers_on_cancelled_sales() -> bool:
:rtype: bool :rtype: bool
""" """
aw = AuctionWallet() wallet_rpc = connect_rpc("wallet")
if aw.connected is False:
logging.error('[ERROR] Auction wallet is not connected. Quitting.')
return False
item_sales = ItemSale.objects.filter(sale_cancelled=True, payment_refunded=False) item_sales = ItemSale.objects.filter(sale_cancelled=True, payment_refunded=False)
for sale in item_sales: for sale in item_sales:
logger.info(f'[INFO] Refunding any sent funds from the buyer for sale #{sale.id}.') logger.info(f'[INFO] Refunding any sent funds from the buyer for sale #{sale.id}.')
sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr sale_total = sale.agreed_price_xmr - sale.platform_fee_xmr
sale_account = aw.wallet.accounts[sale.escrow_account_index] sale_account = wallet_rpc.wallet.accounts[sale.escrow_account_index]
balances = sale_account.balances() balances = sale_account.balances()
logger.info(f'[INFO] Found balances of {balances} XMR for sale #{sale.id}.') logger.info(f'[INFO] Found balances of {balances} XMR for sale #{sale.id}.')
if balances[0] != balances[1]: if balances[0] != balances[1]:

@ -15,6 +15,7 @@
<p class="sale-info">The buyer confirmed receipt of their shipment which means things worked out. It's time for you to get paid!</p> <p class="sale-info">The buyer confirmed receipt of their shipment which means things worked out. It's time for you to get paid!</p>
<p class="sale-info">Your payout address provided during item creation will be paid the accepted bid amount out of the escrow wallet. <em>Please note, transaction fees will come out of the payout total if there are no platform fees!</em></p> <p class="sale-info">Your payout address provided during item creation will be paid the accepted bid amount out of the escrow wallet. <em>Please note, transaction fees will come out of the payout total if there are no platform fees!</em></p>
<hr> <hr>
<p class="sale-info"><strong>Payout Sent</strong>: {{ sale.seller_paid }}</p>
<p class="sale-info"><strong>Accepted Bid (XMR)</strong>: {{ sale.bid.bid_price_xmr }}</p> <p class="sale-info"><strong>Accepted Bid (XMR)</strong>: {{ sale.bid.bid_price_xmr }}</p>
<p class="sale-info"><strong>Platform Fee (XMR)</strong>: {{ sale.platform_fee_xmr }}</p> <p class="sale-info"><strong>Platform Fee (XMR)</strong>: {{ sale.platform_fee_xmr }}</p>
<p class="sale-info"><strong>Network Fee (XMR)</strong>: {{ sale.network_fee_xmr }}</p> <p class="sale-info"><strong>Network Fee (XMR)</strong>: {{ sale.network_fee_xmr }}</p>

@ -27,6 +27,7 @@ DEBUG = os.environ.get('DEBUG', False)
ALLOWED_HOSTS = str(os.environ['ALLOWED_HOSTS']).split(',') ALLOWED_HOSTS = str(os.environ['ALLOWED_HOSTS']).split(',')
ESCROW_PERIOD_DAYS = os.environ.get('ESCROW_PERIOD_DAYS', 30) ESCROW_PERIOD_DAYS = os.environ.get('ESCROW_PERIOD_DAYS', 30)
PLATFORM_FEE_PERCENT = os.environ.get('PLATFORM_FEE_PERCENT', 0) PLATFORM_FEE_PERCENT = os.environ.get('PLATFORM_FEE_PERCENT', 0)
BLOCK_CONFIRMATIONS_RCV = os.environ.get('BLOCK_CONFIRMATIONS_RCV', 3)
# Application definition # Application definition