Add background order updates. Add confirm boxes for Dispute and Fiat Received

This commit is contained in:
Reckless_Satoshi 2022-01-17 15:11:41 -08:00
parent 0db73c7c82
commit 28d18a4842
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 231 additions and 80 deletions

View File

@ -1,11 +1,14 @@
from datetime import time, timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from .lightning.node import LNNode from api.lightning.node import LNNode
from .models import Order, LNPayment, MarketTick, User, Currency from api.models import Order, LNPayment, MarketTick, User, Currency
from decouple import config from decouple import config
from api.tasks import follow_send_payment
import math import math
import ast
FEE = float(config('FEE')) FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE')) BOND_SIZE = float(config('BOND_SIZE'))
@ -140,6 +143,7 @@ class Logics():
cls.settle_bond(order.maker_bond) cls.settle_bond(order.maker_bond)
cls.settle_bond(order.taker_bond) cls.settle_bond(order.taker_bond)
cls.cancel_escrow(order)
order.status = Order.Status.EXP order.status = Order.Status.EXP
order.maker = None order.maker = None
order.taker = None order.taker = None
@ -152,6 +156,7 @@ class Logics():
if maker_is_seller: if maker_is_seller:
cls.settle_bond(order.maker_bond) cls.settle_bond(order.maker_bond)
cls.return_bond(order.taker_bond) cls.return_bond(order.taker_bond)
cls.cancel_escrow(order)
order.status = Order.Status.EXP order.status = Order.Status.EXP
order.maker = None order.maker = None
order.taker = None order.taker = None
@ -161,22 +166,25 @@ class Logics():
# If maker is buyer, settle the taker's bond order goes back to public # If maker is buyer, settle the taker's bond order goes back to public
else: else:
cls.settle_bond(order.taker_bond) cls.settle_bond(order.taker_bond)
cls.cancel_escrow(order)
order.status = Order.Status.PUB order.status = Order.Status.PUB
order.taker = None order.taker = None
order.taker_bond = None order.taker_bond = None
order.trade_escrow = None
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
order.save() order.save()
return True return True
elif order.status == Order.Status.WFI: elif order.status == Order.Status.WFI:
# The trade could happen without a buyer invoice. However, this user # The trade could happen without a buyer invoice. However, this user
# is likely AFK since he did not submit an invoice; will probably # is likely AFK; will probably desert the contract as well.
# desert the contract as well.
maker_is_buyer = cls.is_buyer(order, order.maker) maker_is_buyer = cls.is_buyer(order, order.maker)
# If maker is buyer, settle the bond and order goes to expired # If maker is buyer, settle the bond and order goes to expired
if maker_is_buyer: if maker_is_buyer:
cls.settle_bond(order.maker_bond) cls.settle_bond(order.maker_bond)
cls.return_bond(order.taker_bond) cls.return_bond(order.taker_bond)
cls.return_escrow(order)
order.status = Order.Status.EXP order.status = Order.Status.EXP
order.maker = None order.maker = None
order.taker = None order.taker = None
@ -186,17 +194,19 @@ class Logics():
# If maker is seller settle the taker's bond, order goes back to public # If maker is seller settle the taker's bond, order goes back to public
else: else:
cls.settle_bond(order.taker_bond) cls.settle_bond(order.taker_bond)
cls.return_escrow(order)
order.status = Order.Status.PUB order.status = Order.Status.PUB
order.taker = None order.taker = None
order.taker_bond = None order.taker_bond = None
order.trade_escrow = None
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB]) order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
order.save() order.save()
return True return True
elif order.status == Order.Status.CHA: elif order.status == Order.Status.CHA:
# Another weird case. The time to confirm 'fiat sent' expired. Yet no dispute # Another weird case. The time to confirm 'fiat sent' expired. Yet no dispute
# was opened. A seller-scammer could persuade a buyer to not click "fiat sent" # was opened. Hint: a seller-scammer could persuade a buyer to not click "fiat
# as of now, we assume this is a dispute case by default. # sent", we assume this is a dispute case by default.
cls.open_dispute(order) cls.open_dispute(order)
return True return True
@ -219,12 +229,14 @@ class Logics():
def open_dispute(cls, order, user=None): def open_dispute(cls, order, user=None):
# Always settle the escrow during a dispute (same as with 'Fiat Sent') # Always settle the escrow during a dispute (same as with 'Fiat Sent')
# Dispute winner will have to submit a new invoice.
if not order.trade_escrow.status == LNPayment.Status.SETLED: if not order.trade_escrow.status == LNPayment.Status.SETLED:
cls.settle_escrow(order) cls.settle_escrow(order)
order.is_disputed = True order.is_disputed = True
order.status = Order.Status.DIS order.status = Order.Status.DIS
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.DIS]) order.expires_at = timezone.now() + timedelta(seconds=Order.t_to_expire[Order.Status.DIS])
order.save() order.save()
# User could be None if a dispute is open automatically due to weird expiration. # User could be None if a dispute is open automatically due to weird expiration.
@ -235,6 +247,7 @@ class Logics():
profile.save() profile.save()
return True, None return True, None
def dispute_statement(order, user, statement): def dispute_statement(order, user, statement):
''' Updates the dispute statements in DB''' ''' Updates the dispute statements in DB'''
if not order.status == Order.Status.DIS: if not order.status == Order.Status.DIS:
@ -319,16 +332,18 @@ class Logics():
def add_profile_rating(profile, rating): def add_profile_rating(profile, rating):
''' adds a new rating to a user profile''' ''' adds a new rating to a user profile'''
# TODO Unsafe, does not update ratings, it adds more ratings everytime a new rating is clicked.
profile.total_ratings = profile.total_ratings + 1 profile.total_ratings = profile.total_ratings + 1
latest_ratings = profile.latest_ratings latest_ratings = profile.latest_ratings
if len(latest_ratings) <= 1: if latest_ratings == None:
profile.latest_ratings = [rating] profile.latest_ratings = [rating]
profile.avg_rating = rating profile.avg_rating = rating
else: else:
latest_ratings = list(latest_ratings).append(rating) latest_ratings = ast.literal_eval(latest_ratings)
latest_ratings.append(rating)
profile.latest_ratings = latest_ratings profile.latest_ratings = latest_ratings
profile.avg_rating = sum(latest_ratings) / len(latest_ratings) profile.avg_rating = sum(list(map(int, latest_ratings))) / len(latest_ratings) # Just an average, but it is a list of strings. Has to be converted to int.
profile.save() profile.save()
@ -413,15 +428,20 @@ class Logics():
else: else:
return False, {'bad_request':'You cannot cancel this order'} return False, {'bad_request':'You cannot cancel this order'}
def publish_order(order):
if order.status == Order.Status.WFB:
order.status = Order.Status.PUB
# With the bond confirmation the order is extended 'public_order_duration' hours
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
order.save()
return
@classmethod @classmethod
def is_maker_bond_locked(cls, order): def is_maker_bond_locked(cls, order):
if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash): if LNNode.validate_hold_invoice_locked(order.maker_bond.payment_hash):
order.maker_bond.status = LNPayment.Status.LOCKED order.maker_bond.status = LNPayment.Status.LOCKED
order.maker_bond.save() order.maker_bond.save()
order.status = Order.Status.PUB cls.publish_order(order)
# With the bond confirmation the order is extended 'public_order_duration' hours
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
order.save()
return True return True
return False return False
@ -467,13 +487,12 @@ class Logics():
return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis} return True, {'bond_invoice':hold_payment['invoice'], 'bond_satoshis':bond_satoshis}
@classmethod @classmethod
def is_taker_bond_locked(cls, order): def finalize_contract(cls, order):
if order.taker_bond.status == LNPayment.Status.LOCKED: ''' When the taker locks the taker_bond
return True the contract is final '''
if LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash):
# THE TRADE AMOUNT IS FINAL WITH THE CONFIRMATION OF THE TAKER BOND! # THE TRADE AMOUNT IS FINAL WITH THE CONFIRMATION OF THE TAKER BOND!
# (This is the last update to "last_satoshis", it becomes the escrow amount next!) # (This is the last update to "last_satoshis", it becomes the escrow amount next)
order.last_satoshis = cls.satoshis_now(order) order.last_satoshis = cls.satoshis_now(order)
order.taker_bond.status = LNPayment.Status.LOCKED order.taker_bond.status = LNPayment.Status.LOCKED
order.taker_bond.save() order.taker_bond.save()
@ -492,6 +511,14 @@ class Logics():
order.status = Order.Status.WF2 order.status = Order.Status.WF2
order.save() order.save()
return True return True
@classmethod
def is_taker_bond_locked(cls, order):
if order.taker_bond.status == LNPayment.Status.LOCKED:
return True
if LNNode.validate_hold_invoice_locked(order.taker_bond.payment_hash):
cls.finalize_contract(order)
return True
return False return False
@classmethod @classmethod
@ -618,11 +645,17 @@ class Logics():
order.trade_escrow.status = LNPayment.Status.RETNED order.trade_escrow.status = LNPayment.Status.RETNED
return True return True
def cancel_escrow(order):
'''returns the trade escrow'''
# Same as return escrow, but used when the invoice was never LOCKED
if LNNode.cancel_return_hold_invoice(order.trade_escrow.payment_hash):
order.trade_escrow.status = LNPayment.Status.CANCEL
return True
def return_bond(bond): def return_bond(bond):
'''returns a bond''' '''returns a bond'''
if bond == None: if bond == None:
return return
try: try:
LNNode.cancel_return_hold_invoice(bond.payment_hash) LNNode.cancel_return_hold_invoice(bond.payment_hash)
bond.status = LNPayment.Status.RETNED bond.status = LNPayment.Status.RETNED
@ -631,10 +664,12 @@ class Logics():
if 'invoice already settled' in str(e): if 'invoice already settled' in str(e):
bond.status = LNPayment.Status.SETLED bond.status = LNPayment.Status.SETLED
return True return True
else:
raise e
def cancel_bond(bond): def cancel_bond(bond):
'''cancel a bond''' '''cancel a bond'''
# Same as return bond, but used when the invoice was never accepted # Same as return bond, but used when the invoice was never LOCKED
if bond == None: if bond == None:
return True return True
try: try:
@ -645,11 +680,12 @@ class Logics():
if 'invoice already settled' in str(e): if 'invoice already settled' in str(e):
bond.status = LNPayment.Status.SETLED bond.status = LNPayment.Status.SETLED
return True return True
else:
raise e
def pay_buyer_invoice(order): def pay_buyer_invoice(order):
''' Pay buyer invoice''' ''' Pay buyer invoice'''
# TODO ERROR HANDLING suceeded, context = follow_send_payment(order.buyer_invoice)
suceeded, context = LNNode.pay_invoice(order.buyer_invoice.invoice, order.buyer_invoice.num_satoshis)
return suceeded, context return suceeded, context
@classmethod @classmethod
@ -703,11 +739,15 @@ class Logics():
# If the trade is finished # If the trade is finished
if order.status > Order.Status.PAY: if order.status > Order.Status.PAY:
# if maker, rates taker # if maker, rates taker
if order.maker == user: if order.maker == user and order.maker_rated == False:
cls.add_profile_rating(order.taker.profile, rating) cls.add_profile_rating(order.taker.profile, rating)
order.maker_rated = True
order.save()
# if taker, rates maker # if taker, rates maker
if order.taker == user: if order.taker == user and order.taker_rated == False:
cls.add_profile_rating(order.maker.profile, rating) cls.add_profile_rating(order.maker.profile, rating)
order.taker_rated = True
order.save()
else: else:
return False, {'bad_request':'You cannot rate your counterparty yet.'} return False, {'bad_request':'You cannot rate your counterparty yet.'}

View File

@ -37,5 +37,6 @@ class Command(BaseCommand):
if Logics.order_expires(order): # Order send to expire here if Logics.order_expires(order): # Order send to expire here
debug['expired_orders'].append({idx:context}) debug['expired_orders'].append({idx:context})
if debug['num_expired_orders'] > 0:
self.stdout.write(str(timezone.now())) self.stdout.write(str(timezone.now()))
self.stdout.write(str(debug)) self.stdout.write(str(debug))

View File

@ -1,21 +1,25 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from api.lightning.node import LNNode from api.lightning.node import LNNode
from api.models import LNPayment, Order
from api.logics import Logics
from django.utils import timezone
from datetime import timedelta
from decouple import config from decouple import config
from base64 import b64decode from base64 import b64decode
from api.models import LNPayment
import time import time
MACAROON = b64decode(config('LND_MACAROON_BASE64')) MACAROON = b64decode(config('LND_MACAROON_BASE64'))
class Command(BaseCommand): class Command(BaseCommand):
''' '''
Background: SubscribeInvoices stub iterator would be great to use here Background: SubscribeInvoices stub iterator would be great to use here.
however it only sends updates when the invoice is OPEN (new) or SETTLED. However, it only sends updates when the invoice is OPEN (new) or SETTLED.
We are very interested on the other two states (CANCELLED and ACCEPTED). We are very interested on the other two states (CANCELLED and ACCEPTED).
Therefore, this thread (follow_invoices) will iterate over all LNpayment Therefore, this thread (follow_invoices) will iterate over all LNpayment
objects and do InvoiceLookupV2 to update their state 'live' ''' objects and do InvoiceLookupV2 every X seconds to update their state 'live'
'''
help = 'Follows all active hold invoices' help = 'Follows all active hold invoices'
@ -27,10 +31,10 @@ class Command(BaseCommand):
until settled or canceled''' until settled or canceled'''
lnd_state_to_lnpayment_status = { lnd_state_to_lnpayment_status = {
0: LNPayment.Status.INVGEN, 0: LNPayment.Status.INVGEN, # OPEN
1: LNPayment.Status.SETLED, 1: LNPayment.Status.SETLED, # SETTLED
2: LNPayment.Status.CANCEL, 2: LNPayment.Status.CANCEL, # CANCELLED
3: LNPayment.Status.LOCKED 3: LNPayment.Status.LOCKED # ACCEPTED
} }
stub = LNNode.invoicesstub stub = LNNode.invoicesstub
@ -45,6 +49,7 @@ class Command(BaseCommand):
debug = {} debug = {}
debug['num_active_invoices'] = len(queryset) debug['num_active_invoices'] = len(queryset)
debug['invoices'] = [] debug['invoices'] = []
at_least_one_changed = False
for idx, hold_lnpayment in enumerate(queryset): for idx, hold_lnpayment in enumerate(queryset):
old_status = LNPayment.Status(hold_lnpayment.status).label old_status = LNPayment.Status(hold_lnpayment.status).label
@ -56,29 +61,55 @@ class Command(BaseCommand):
# If it fails at finding the invoice it has been canceled. # If it fails at finding the invoice it has been canceled.
# On RoboSats DB we make a distinction between cancelled and returned (LND does not) # On RoboSats DB we make a distinction between cancelled and returned (LND does not)
except: except Exception as e:
if 'unable to locate invoice' in str(e):
hold_lnpayment.status = LNPayment.Status.CANCEL hold_lnpayment.status = LNPayment.Status.CANCEL
continue else:
self.stdout.write(str(e))
new_status = LNPayment.Status(hold_lnpayment.status).label new_status = LNPayment.Status(hold_lnpayment.status).label
# Only save the hold_payments that change (otherwise this function does not scale) # Only save the hold_payments that change (otherwise this function does not scale)
changed = not old_status==new_status changed = not old_status==new_status
if changed: if changed:
# self.handle_status_change(hold_lnpayment, old_status)
hold_lnpayment.save() hold_lnpayment.save()
self.update_order_status(hold_lnpayment)
# Report for debugging # Report for debugging
new_status = LNPayment.Status(hold_lnpayment.status).label new_status = LNPayment.Status(hold_lnpayment.status).label
debug['invoices'].append({idx:{ debug['invoices'].append({idx:{
'payment_hash': str(hold_lnpayment.payment_hash), 'payment_hash': str(hold_lnpayment.payment_hash),
'status_changed': not old_status==new_status,
'old_status': old_status, 'old_status': old_status,
'new_status': new_status, 'new_status': new_status,
}}) }})
at_least_one_changed = at_least_one_changed or changed
debug['time']=time.time()-t0 debug['time']=time.time()-t0
self.stdout.write(str(timezone.now())+str(debug)) if at_least_one_changed:
self.stdout.write(str(timezone.now()))
self.stdout.write(str(debug))
def update_order_status(self, lnpayment):
''' Background process following LND hold invoices
might catch LNpayments changing status. If they do,
the order status might have to change status too.'''
# If the LNPayment goes to LOCKED (ACCEPTED)
if lnpayment.status == LNPayment.Status.LOCKED:
# It is a maker bond => Publish order.
order = lnpayment.order_made
if not order == None:
Logics.publish_order(order)
return
# It is a taker bond => close contract.
order = lnpayment.order_taken
if not order == None:
if order.status == Order.Status.TAK:
Logics.finalize_contract(order)
return

View File

@ -146,16 +146,16 @@ class Order(models.Model):
# LNpayments # LNpayments
# Order collateral # Order collateral
maker_bond = models.ForeignKey(LNPayment, related_name='maker_bond', on_delete=models.SET_NULL, null=True, default=None, blank=True) maker_bond = models.OneToOneField(LNPayment, related_name='order_made', on_delete=models.SET_NULL, null=True, default=None, blank=True)
taker_bond = models.ForeignKey(LNPayment, related_name='taker_bond', on_delete=models.SET_NULL, null=True, default=None, blank=True) taker_bond = models.OneToOneField(LNPayment, related_name='order_taken', on_delete=models.SET_NULL, null=True, default=None, blank=True)
trade_escrow = models.ForeignKey(LNPayment, related_name='trade_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True) trade_escrow = models.OneToOneField(LNPayment, related_name='order_escrow', on_delete=models.SET_NULL, null=True, default=None, blank=True)
# buyer payment LN invoice # buyer payment LN invoice
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True) buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True)
# Unused so far. Cancel LN invoices // these are only needed to charge lower-than-bond amounts. E.g., a taken order has a small cost if cancelled, to avoid DDOSing. # ratings
# maker_cancel = models.ForeignKey(LNPayment, related_name='maker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True) maker_rated = models.BooleanField(default=False, null=False)
# taker_cancel = models.ForeignKey(LNPayment, related_name='taker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True) taker_rated = models.BooleanField(default=False, null=False)
t_to_expire = { t_to_expire = {
0 : int(config('EXP_MAKER_BOND_INVOICE')) , # 'Waiting for maker bond' 0 : int(config('EXP_MAKER_BOND_INVOICE')) , # 'Waiting for maker bond'
@ -183,6 +183,7 @@ class Order(models.Model):
# Make relational back to ORDER # Make relational back to ORDER
return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}') return (f'Order {self.id}: {self.Types(self.type).label} BTC for {float(self.amount)} {self.currency}')
@receiver(pre_delete, sender=Order) @receiver(pre_delete, sender=Order)
def delete_lnpayment_at_order_deletion(sender, instance, **kwargs): def delete_lnpayment_at_order_deletion(sender, instance, **kwargs):
to_delete = (instance.maker_bond, instance.buyer_invoice, instance.taker_bond, instance.trade_escrow) to_delete = (instance.maker_bond, instance.buyer_invoice, instance.taker_bond, instance.trade_escrow)

View File

@ -61,7 +61,7 @@ export default class OrderPage extends Component {
"8": 10000, //'Waiting only for buyer invoice' "8": 10000, //'Waiting only for buyer invoice'
"9": 10000, //'Sending fiat - In chatroom' "9": 10000, //'Sending fiat - In chatroom'
"10": 15000, //'Fiat sent - In chatroom' "10": 15000, //'Fiat sent - In chatroom'
"11": 300000, //'In dispute' "11": 60000, //'In dispute'
"12": 9999999,//'Collaboratively cancelled' "12": 9999999,//'Collaboratively cancelled'
"13": 120000, //'Sending satoshis to buyer' "13": 120000, //'Sending satoshis to buyer'
"14": 9999999,//'Sucessful trade' "14": 9999999,//'Sucessful trade'

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Link, Paper, Rating, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon} from "@mui/material" import { Link, Paper, Rating, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
import QRCode from "react-qr-code"; import QRCode from "react-qr-code";
import Chat from "./Chat" import Chat from "./Chat"
@ -37,11 +37,100 @@ export default class TradeBox extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
openConfirmFiatReceived: false,
openConfirmDispute: false,
badInvoice: false, badInvoice: false,
badStatement: false, badStatement: false,
} }
} }
handleClickOpenConfirmDispute = () => {
this.setState({openConfirmDispute: true});
};
handleClickCloseConfirmDispute = () => {
this.setState({openConfirmDispute: false});
};
handleClickAgreeDisputeButton=()=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "dispute",
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => (this.props.data = data));
this.handleClickCloseConfirmDispute();
}
ConfirmDisputeDialog =() =>{
return(
<Dialog
open={this.state.openConfirmDispute}
onClose={this.handleClickCloseConfirmDispute}
aria-labelledby="open-dispute-dialog-title"
aria-describedby="open-dispute-dialog-description"
>
<DialogTitle id="open-dispute-dialog-title">
{"Do you want to open a dispute?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
The RoboSats staff will examine the statements and evidence provided by the participants.
It is best if you provide a burner contact method on your statement for the staff to contact you.
The satoshis in the trade escrow will be sent to the dispute winner, while the dispute
loser will lose the bond.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseConfirmDispute} autoFocus>Disagree</Button>
<Button onClick={this.handleClickAgreeDisputeButton}> Agree </Button>
</DialogActions>
</Dialog>
)
}
handleClickOpenConfirmFiatReceived = () => {
this.setState({openConfirmFiatReceived: true});
};
handleClickCloseConfirmFiatReceived = () => {
this.setState({openConfirmFiatReceived: false});
};
handleClickTotallyConfirmFiatReceived = () =>{
this.handleClickConfirmButton();
this.handleClickCloseConfirmFiatReceived();
};
ConfirmFiatReceivedDialog =() =>{
return(
<Dialog
open={this.state.openConfirmFiatReceived}
onClose={this.handleClickCloseConfirmFiatReceived}
aria-labelledby="fiat-received-dialog-title"
aria-describedby="fiat-received-dialog-description"
>
<DialogTitle id="open-dispute-dialog-title">
{"Confirm you received " +this.props.data.currencyCode+ "?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Confirming that you received the fiat will finalize the trade. The satoshis
in the escrow will be released to the buyer. Only confirm after the {this.props.data.currencyCode+ " "}
has arrived to your account. In addition, if you have received {this.props.data.currencyCode+ " "}
and do not confirm the receipt, you risk losing your bond.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickCloseConfirmFiatReceived} autoFocus>Go back</Button>
<Button onClick={this.handleClickTotallyConfirmFiatReceived}> Confirm </Button>
</DialogActions>
</Dialog>
)
}
showQRInvoice=()=>{ showQRInvoice=()=>{
return ( return (
<Grid container spacing={1}> <Grid container spacing={1}>
@ -275,7 +364,7 @@ export default class TradeBox extends Component {
/> />
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button onClick={this.handleClickSubmitStatementButton} variant='contained' color='primary'>Submit</Button> <Button onClick={this.handleClickSubmitInvoiceButton} variant='contained' color='primary'>Submit</Button>
</Grid> </Grid>
{this.showBondIsLocked()} {this.showBondIsLocked()}
@ -382,18 +471,7 @@ export default class TradeBox extends Component {
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.props.data = data)); .then((data) => (this.props.data = data));
} }
handleClickOpenDisputeButton=()=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
'action': "dispute",
}),
};
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
.then((response) => response.json())
.then((data) => (this.props.data = data));
}
handleRatingChange=(e)=>{ handleRatingChange=(e)=>{
const requestOptions = { const requestOptions = {
method: 'POST', method: 'POST',
@ -419,11 +497,9 @@ handleRatingChange=(e)=>{
} }
showFiatReceivedButton(){ showFiatReceivedButton(){
// TODO, show alert and ask for double confirmation (Have you check you received the fiat? Confirming fiat received settles the trade.)
// Ask for double confirmation.
return( return(
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button defaultValue="confirm" variant='contained' color='secondary' onClick={this.handleClickConfirmButton}>Confirm {this.props.data.currencyCode} received</Button> <Button defaultValue="confirm" variant='contained' color='secondary' onClick={this.handleClickOpenConfirmFiatReceived}>Confirm {this.props.data.currencyCode} received</Button>
</Grid> </Grid>
) )
} }
@ -432,7 +508,7 @@ handleRatingChange=(e)=>{
// TODO, show alert about how opening a dispute might involve giving away personal data and might mean losing the bond. Ask for double confirmation. // TODO, show alert about how opening a dispute might involve giving away personal data and might mean losing the bond. Ask for double confirmation.
return( return(
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button color="inherit" onClick={this.handleClickOpenDisputeButton}>Open Dispute</Button> <Button color="inherit" onClick={this.handleClickOpenConfirmDispute}>Open Dispute</Button>
</Grid> </Grid>
) )
} }
@ -487,7 +563,7 @@ handleRatingChange=(e)=>{
<Rating name="size-large" defaultValue={2} size="large" onChange={this.handleRatingChange} /> <Rating name="size-large" defaultValue={2} size="large" onChange={this.handleRatingChange} />
</Grid> </Grid>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Button color='primary' to='/' component={Link}>Start Again</Button> <Button color='primary' href='/' component="a">Start Again</Button>
</Grid> </Grid>
</Grid> </Grid>
) )
@ -497,6 +573,8 @@ handleRatingChange=(e)=>{
render() { render() {
return ( return (
<Grid container spacing={1} style={{ width:330}}> <Grid container spacing={1} style={{ width:330}}>
<this.ConfirmDisputeDialog/>
<this.ConfirmFiatReceivedDialog/>
<Grid item xs={12} align="center"> <Grid item xs={12} align="center">
<Typography component="h5" variant="h5"> <Typography component="h5" variant="h5">
Contract Box Contract Box