complete sale workflow and views

pull/3/head
lalanza808 5 years ago
parent cb9527339d
commit fc9c09e226

@ -0,0 +1,28 @@
# Generated by Django 2.2.8 on 2020-01-07 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sales', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='itemsale',
name='buyer_notified_of_shipment',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='itemsale',
name='seller_notified_of_payout',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='itemsale',
name='seller_notified_of_receipt',
field=models.BooleanField(default=False),
),
]

@ -16,8 +16,10 @@ class ItemSale(models.Model):
received_payment_xmr = models.FloatField(default=0.0) received_payment_xmr = models.FloatField(default=0.0)
escrow_period_days = models.PositiveSmallIntegerField(default=settings.ESCROW_PERIOD_DAYS) escrow_period_days = models.PositiveSmallIntegerField(default=settings.ESCROW_PERIOD_DAYS)
buyer_notified = models.BooleanField(default=False) buyer_notified = models.BooleanField(default=False)
buyer_notified_of_shipment = models.BooleanField(default=False)
payment_received = models.BooleanField(default=False) payment_received = models.BooleanField(default=False)
seller_notified = models.BooleanField(default=False) seller_notified = models.BooleanField(default=False)
seller_notified_of_receipt = models.BooleanField(default=False)
payment_refunded = models.BooleanField(default=False) payment_refunded = models.BooleanField(default=False)
item_shipped = models.BooleanField(default=False) item_shipped = models.BooleanField(default=False)
item_received = models.BooleanField(default=False) item_received = models.BooleanField(default=False)
@ -25,6 +27,7 @@ class ItemSale(models.Model):
seller_disputed = models.BooleanField(default=False) seller_disputed = models.BooleanField(default=False)
escrow_complete = models.BooleanField(default=False) escrow_complete = models.BooleanField(default=False)
seller_paid = models.BooleanField(default=False) seller_paid = models.BooleanField(default=False)
seller_notified_of_payout = models.BooleanField(default=False)
platform_paid = models.BooleanField(default=False) platform_paid = models.BooleanField(default=False)
sale_finalized = models.BooleanField(default=False) sale_finalized = models.BooleanField(default=False)

@ -33,6 +33,22 @@ class EmailTemplate:
) )
self.subject = ''.join(subject.splitlines()) self.subject = ''.join(subject.splitlines())
self.body = body 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
@periodic_task(crontab(minute='*/3')) @periodic_task(crontab(minute='*/3'))
@ -44,12 +60,7 @@ def notify_buyer_of_pending_sale():
scenario='sale_created', scenario='sale_created',
role='buyer' role='buyer'
) )
sent = send_mail( sent = email_template.send()
email_template.subject,
email_template.body,
settings.DEFAULT_FROM_EMAIL,
[sale.bid.bidder.email]
)
if sent == 1: if sent == 1:
sale.buyer_notified = True sale.buyer_notified = True
sale.save() sale.save()
@ -57,21 +68,50 @@ def notify_buyer_of_pending_sale():
else: else:
return False return False
@periodic_task(crontab(minute='*/3'))
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:
email_template = EmailTemplate(
item=sale,
scenario='item_shipped',
role='buyer'
)
sent = email_template.send()
if sent == 1:
sale.buyer_notified_of_shipment = True
sale.save()
return True
else:
return False
@periodic_task(crontab(minute='*/3'))
def notify_seller_of_shipment_receipt():
item_sales = ItemSale.objects.filter(item_shipped=True, item_received=False).filter(seller_notified_of_receipt=False)
for sale in item_sales:
email_template = EmailTemplate(
item=sale,
scenario='item_shipped',
role='buyer'
)
sent = email_template.send()
if sent == 1:
sale.seller_notified_of_receipt = True
sale.save()
return True
else:
return False
@periodic_task(crontab(minute='*/2')) @periodic_task(crontab(minute='*/2'))
def notify_seller_of_funds_received(): def notify_seller_of_funds_received():
item_sales = ItemSale.objects.filter(seller_notified=False, buyer_notified=True, payment_received=True) item_sales = ItemSale.objects.filter(seller_notified=False, buyer_notified=True).filter(payment_received=True)
for sale in item_sales: for sale in item_sales:
email_template = EmailTemplate( email_template = EmailTemplate(
item=sale, item=sale,
scenario='funds_received', scenario='funds_received',
role='seller' role='seller'
) )
sent = send_mail( sent = email_template.send()
email_template.subject,
email_template.body,
settings.DEFAULT_FROM_EMAIL,
[sale.item.owner.email]
)
if sent == 1: if sent == 1:
sale.seller_notified = True sale.seller_notified = True
sale.save() sale.save()
@ -79,7 +119,42 @@ def notify_seller_of_funds_received():
else: else:
return False return False
@periodic_task(crontab(minute='*/10')) @periodic_task(crontab(minute='*/12'))
def pay_sellers_on_sold_items():
aw = AuctionWallet()
if aw.connected is False:
return False
item_sales = ItemSale.objects.filter(item_received=True, payment_received=True).filter(seller_paid=False)
for sale in item_sales:
email_template = EmailTemplate(
item=sale,
scenario='sale_completed',
role='seller'
)
if sale.seller_notified_of_payout is False:
sent = email_template.send()
sale.seller_notified_of_payout = True
sale.save()
try:
txs = aw.wallet.accounts[sale.escrow_account_index].transfer(
sale.item.payout_address, sale.agreed_price_xmr, relay=True
)
print(txs)
if txs:
sale.seller_paid = True
sale.escrow_complete = True
sale.save()
return True
else:
return False
except Exception as e:
print('unable to make payment: ', e)
return False
@periodic_task(crontab(minute='*/5'))
def poll_for_buyer_escrow_payments(): def poll_for_buyer_escrow_payments():
aw = AuctionWallet() aw = AuctionWallet()
item_sales = ItemSale.objects.filter(payment_received=False) item_sales = ItemSale.objects.filter(payment_received=False)

@ -4,4 +4,6 @@ from . import views
urlpatterns = [ urlpatterns = [
path('<int:bid_id>/', views.get_sale, name='get_sale'), path('<int:bid_id>/', views.get_sale, name='get_sale'),
path('<int:sale_id>/confirm_shipment/', views.confirm_shipment, name='confirm_shipment'),
path('<int:sale_id>/confirm_receipt/', views.confirm_receipt, name='confirm_receipt')
] ]

@ -9,7 +9,6 @@ from bids.models import ItemBid
from sales.models import ItemSale from sales.models import ItemSale
@login_required @login_required
def get_sale(request, bid_id): def get_sale(request, bid_id):
bid = ItemBid.objects.get(id=bid_id) bid = ItemBid.objects.get(id=bid_id)
@ -32,3 +31,33 @@ def get_sale(request, bid_id):
} }
return render(request, 'sales/get_sale.html', context) return render(request, 'sales/get_sale.html', context)
@login_required
def confirm_shipment(request, sale_id):
sale = ItemSale.objects.get(id=sale_id)
# Only proceed if current user is the seller
if request.user == sale.item.owner:
sale.item_shipped = True
sale.save()
messages.success(request, "Package sent, buyer notified!")
return HttpResponseRedirect(reverse('get_sale', args=[sale.bid.id]))
else:
messages.error(request, "You can't confirm a package shipment for an item you don't own.")
return HttpResponseRedirect(reverse('home'))
@login_required
def confirm_receipt(request, sale_id):
sale = ItemSale.objects.get(id=sale_id)
# Do not proceed unless current user is the buyer
if request.user == sale.bid.bidder:
sale.item_received = True
sale.save()
messages.success(request, "Item received!")
return HttpResponseRedirect(reverse('get_sale', args=[sale.bid.id]))
else:
messages.error(request, "You can't confirm receipt of an item you didn't purchase.")
return HttpResponseRedirect(reverse('home'))

@ -22,17 +22,18 @@
<h2>Need a Wallet?</h2> <h2>Need a Wallet?</h2>
<p class="sale-info">Try out these popular Monero wallet projects:</p> <p class="sale-info">Try out these popular Monero wallet projects:</p>
<ul> <ul>
<li><a href="https://cakewallet.com/" target="_blank">CakeWallet</a></li> <li><a href="https://www.getmonero.org/downloads/" target="_blank">Official Community Funded Monero Software (desktop)</a></li>
<li><a href="https://www.monerujo.io/" target="_blank">Monerujo</a></li> <li><a href="https://cakewallet.com/" target="_blank">CakeWallet (iOS, Android)</a></li>
<li><a href="https://mymonero.com/" target="_blank">MyMonero</a></li> <li><a href="https://www.monerujo.io/" target="_blank">Monerujo (Android)</a></li>
<li><a href="https://mymonero.com/" target="_blank">MyMonero (Desktop, Web)</a></li>
</ul> </ul>
{% elif sale.payment_received %} {% elif sale.payment_received and sale.item_shipped == False %}
<p class="sale-info">Congratulations {{ sale.bid.bidder.username }},</p> <p class="sale-info">Congratulations {{ sale.bid.bidder.username }},</p>
<p class="sale-info">Your funds have been confirmed!</p> <p class="sale-info">Your funds have been confirmed!</p>
<p class="sale-info"> <p class="sale-info">
The seller has been notified of the proof of payment and has been provided with your shipping address. The seller has been notified of the proof of payment and has been provided with your shipping address.
Please make sure it is correct and will result in successful package delivery. Please make sure it is correct and will result in successful package delivery.
You can edit the address here: <a href="{% url 'edit_shipping' %}">Edit Shipping Address</a> You can edit the address here: <a href="{% url 'edit_shipping' %}" target="_blank">Edit Shipping Address</a>
</p> </p>
<p class="sale-info"><strong>Address 1</strong>: {{ shipping_address.address1 }}</p> <p class="sale-info"><strong>Address 1</strong>: {{ shipping_address.address1 }}</p>
<p class="sale-info"><strong>Address 2</strong>: {{ shipping_address.address2 }}</p> <p class="sale-info"><strong>Address 2</strong>: {{ shipping_address.address2 }}</p>
@ -40,6 +41,29 @@
<p class="sale-info"><strong>State</strong>: {{ shipping_address.state }}</p> <p class="sale-info"><strong>State</strong>: {{ shipping_address.state }}</p>
<p class="sale-info"><strong>Country</strong>: {{ shipping_address.country }}</p> <p class="sale-info"><strong>Country</strong>: {{ shipping_address.country }}</p>
<p class="sale-info"><strong>Zip</strong>: {{ shipping_address.zip }}</p> <p class="sale-info"><strong>Zip</strong>: {{ shipping_address.zip }}</p>
{% elif sale.item_shipped and sale.item_received == False %}
<p class="sale-info">Congratulations {{ sale.bid.bidder.username }},</p>
<p class="sale-info">
The seller has shipped the item, now you need to wait for it to arrive.
When it does arrive please click the button below to confirm.
</p>
<p class="sale-info">Stay tuned for updates and thanks for using {{ site_meta.name }}!</p>
<img src="{% static 'images/monero-symbol-800.png' %}" width=200>
<hr>
<hr>
<p>Click here when you have received your package:</p>
<p class="sale-info"><a href="{% url 'confirm_receipt' sale.id %}" class="button">Confirm Item Received</a></p>
{% elif sale.item_received %}
<p class="sale-info">Hey {{ sale.bid.bidder.username }},</p>
<p class="sale-info">It sounds like your order was successful. The seller will be sent their funds from the transaction out of the escrow wallet. You are good to go!</p>
<p class="sale-info">Thanks for using {{ site_meta.name }}!</p>
<hr>
<p class="sale-info">Please provide me with any feedback so I can make the process better in the future.</p>
<ul>
<li><a href="https://github.com/lalanza808/xmrauctions">Github</a>
<li><a href="https://twitter.com/lza_menace">Twitter</a></li>
<li><a href="https://www.reddit.com/user/catacombkid1">Reddit</a></li>
</ul>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -55,7 +79,7 @@
No action is needed from you at this time, but you will be notified you when there is. No action is needed from you at this time, but you will be notified you when there is.
</p> </p>
<p class="sale-info">Congratulations on the sale!</p> <p class="sale-info">Congratulations on the sale!</p>
{% elif sale.payment_received %} {% elif sale.payment_received and sale.item_shipped == False %}
<p class="sale-info">Congratulations {{ sale.item.owner.username }},</p> <p class="sale-info">Congratulations {{ sale.item.owner.username }},</p>
<p class="sale-info"> <p class="sale-info">
The bidder for your item has sent the proper amount of funds to the escrow wallet address. The bidder for your item has sent the proper amount of funds to the escrow wallet address.
@ -68,8 +92,28 @@
<p class="sale-info"><strong>State</strong>: {{ shipping_address.state }}</p> <p class="sale-info"><strong>State</strong>: {{ shipping_address.state }}</p>
<p class="sale-info"><strong>Country</strong>: {{ shipping_address.country }}</p> <p class="sale-info"><strong>Country</strong>: {{ shipping_address.country }}</p>
<p class="sale-info"><strong>Zip</strong>: {{ shipping_address.zip }}</p> <p class="sale-info"><strong>Zip</strong>: {{ shipping_address.zip }}</p>
<br>
<p class="sale-info">If you have shipped the item please confirm by clicking this button:</p>
<p class="sale-info"><a href="{% url 'confirm_shipment' sale.id %}" class="button">Confirm Item Shipped</a></p>
{% elif sale.item_shipped and sale.item_received == False %}
<p class="sale-info">Hello {{ sale.item.owner.username }},</p>
<p class="sale-info">You've shipped the item, now you need to wait for the buyer to confirm they received it on their end.</p>
<p class="sale-info">Stay tuned for updates and thanks for using {{ site_meta.name }}!</p>
<img src="{% static 'images/monero-symbol-800.png' %}" width=200>
{% elif sale.item_received %}
<p class="sale-info">Hey {{ sale.item.owner.username }},</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.<p>
<p class="sale-info"><strong>Payout Address</strong>: {{ sale.item.payout_address }}<p>
<p class="sale-info">Thanks for using {{ site_meta.name }}!</p>
<hr>
<p class="sale-info">Please provide me with any feedback so I can make the process better in the future.</p>
<li><a href="https://github.com/lalanza808/xmrauctions">Github</a>
<li><a href="https://twitter.com/lza_menace">Twitter</a></li>
<li><a href="https://www.reddit.com/user/catacombkid1">Reddit</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if site_meta.debug %} {% if site_meta.debug %}
<hr> <hr>
<h2>Debug Info</h2> <h2>Debug Info</h2>

@ -1 +1 @@
[{{ site_name }}] Funds Received! (#{{ sale.bid.id }}) [{{ site_name }}] Funds Received! (#{{ sale.id }})

@ -0,0 +1,9 @@
Congratulations {{ sale.bid.bidder }},
Your new item "{{ sale.item.name }}" (#{{ sale.item.id }}) was confirmed by the seller to have shipped. Please expect your package in the mail over the following days/weeks depending on the seller's location.
You can see more information about the sale at the following URL:
https://{{ site_url }}{{ sale_path }}
Thanks for using {{ site_name }}!

@ -0,0 +1 @@
[{{ site_name }}] Item Shipped! (#{{ sale.id }})

@ -0,0 +1,9 @@
Congratulations {{ sale.item.owner }},
The sale of your item, "{{ sale.item.name }}" (#{{ sale.item.id }}), has concluded. You were paid out for your transaction now that the escrow process is closed.
The sale page will remain for 30 more days before being removed from the database. Capture any final details you need.
https://{{ site_url }}{{ sale_path }}
Thanks for using {{ site_name }}!

@ -0,0 +1 @@
[{{ site_name }}] Sale completed! (#{{ sale.id }})

@ -1 +1 @@
[{{ site_name }}] Sale Created! (#{{ sale.bid.id }}) [{{ site_name }}] Sale Created! (#{{ sale.id }})