diff --git a/core/helpers/__init__.py b/core/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/helpers/email_template.py b/core/helpers/email_template.py new file mode 100644 index 0000000..2adf5ab --- /dev/null +++ b/core/helpers/email_template.py @@ -0,0 +1,42 @@ +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 + + +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 \ No newline at end of file diff --git a/sales/tasks/__init__.py b/sales/tasks/__init__.py new file mode 100644 index 0000000..8e816fb --- /dev/null +++ b/sales/tasks/__init__.py @@ -0,0 +1 @@ +from sales.tasks import notifications, payments, cleanup \ No newline at end of file diff --git a/sales/tasks/cleanup.py b/sales/tasks/cleanup.py new file mode 100644 index 0000000..96b699e --- /dev/null +++ b/sales/tasks/cleanup.py @@ -0,0 +1,12 @@ +import logging +from huey import crontab +from huey.contrib.djhuey import periodic_task + +logger = logging.getLogger('django.server') + +@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() \ No newline at end of file diff --git a/sales/tasks/notifications.py b/sales/tasks/notifications.py new file mode 100644 index 0000000..98fa2ce --- /dev/null +++ b/sales/tasks/notifications.py @@ -0,0 +1,54 @@ +import logging +from huey import crontab +from huey.contrib.djhuey import periodic_task +from core.helpers.email_template import EmailTemplate +from core.models import UserShippingAddress +from sales.models import ItemSale + + +logger = logging.getLogger('django.server') + +@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() diff --git a/sales/tasks.py b/sales/tasks/payments.py similarity index 58% rename from sales/tasks.py rename to sales/tasks/payments.py index 79f3e31..de1d3b4 100644 --- a/sales/tasks.py +++ b/sales/tasks/payments.py @@ -3,110 +3,17 @@ 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='*')) +@periodic_task(crontab(minute='*/3')) def poll_for_buyer_escrow_payments(): aw = AuctionWallet() if aw.connected is False: - logging.error('Auction wallet is not connected. Quitting.') + logging.error('[ERROR] Auction wallet is not connected. Quitting.') return False item_sales = ItemSale.objects.filter(payment_received=False) @@ -121,12 +28,11 @@ def poll_for_buyer_escrow_payments(): sale.save() - -@periodic_task(crontab(minute='*')) +@periodic_task(crontab(minute='*/3')) def pay_sellers_on_sold_items(): aw = AuctionWallet() if aw.connected is False: - logging.error('Auction wallet is not connected. Quitting.') + 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) @@ -142,7 +48,6 @@ def pay_sellers_on_sold_items(): sale.item.payout_address, Decimal(.01), relay=False ) new_total = sale_total - float(_tx[0].fee) - logger.info(f'[INFO] Sending {new_total} XMR from wallet account #{sale.escrow_account_index} to item owner\'s payout address for sale #{sale.id}.') # Make the transaction with network fee removed tx = sale_account.transfer( @@ -172,7 +77,7 @@ def pay_sellers_on_sold_items(): def pay_platform_on_sold_items(): aw = AuctionWallet() if aw.connected is False: - logging.error('Auction wallet is not connected. Quitting.') + logging.error('[ERROR] Auction wallet is not connected. Quitting.') return False aof = settings.PLATFORM_WALLET_ADDRESS @@ -201,18 +106,11 @@ def pay_platform_on_sold_items(): 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.') + logging.error('[ERROR] Auction wallet is not connected. Quitting.') return False item_sales = ItemSale.objects.filter(sale_cancelled=True)