mirror of
https://github.com/RoboSats/robosats.git
synced 2025-02-20 12:19:00 +00:00
Add expiration logics. Add dispute statements.
This commit is contained in:
parent
9009f35269
commit
9d883ccc4d
163
api/logics.py
163
api/logics.py
@ -97,21 +97,104 @@ class Logics():
|
||||
|
||||
@classmethod
|
||||
def order_expires(cls, order):
|
||||
''' General case when time runs out. Only
|
||||
used when the maker does not lock a publishing bond'''
|
||||
''' General cases when time runs out.'''
|
||||
|
||||
if order.status == Order.Status.WFB:
|
||||
# Do not change order status if an order in any with
|
||||
# any of these status is sent to expire here
|
||||
do_nothing = [Order.Status.DEL, Order.Status.UCA,
|
||||
Order.Status.EXP, Order.Status.FSE,
|
||||
Order.Status.DIS, Order.Status.CCA,
|
||||
Order.Status.PAY, Order.Status.SUC,
|
||||
Order.Status.FAI, Order.Status.MLD,
|
||||
Order.Status.TLD]
|
||||
|
||||
if order.status in do_nothing:
|
||||
return False
|
||||
|
||||
elif order.status == Order.Status.WFB:
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
return True
|
||||
|
||||
if order.status == Order.Status.PUB:
|
||||
elif order.status == Order.Status.PUB:
|
||||
cls.return_bond(order.maker_bond)
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.TAK:
|
||||
cls.kick_taker(order)
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.WF2:
|
||||
'''Weird case where an order expires and both participants
|
||||
did not proceed with the contract. Likely the site was
|
||||
down or there was a bug. Still bonds must be charged
|
||||
to avoid service DDOS. '''
|
||||
|
||||
cls.settle_bond(order.maker_bond)
|
||||
cls.settle_bond(order.taker_bond)
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.WFE:
|
||||
maker_is_seller = cls.is_seller(order, order.maker)
|
||||
# If maker is seller, settle the bond and order goes to expired
|
||||
if maker_is_seller:
|
||||
cls.settle_bond(order.maker_bond)
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
return True
|
||||
|
||||
# If maker is buyer, settle the taker's bond order goes back to public
|
||||
else:
|
||||
cls.settle_bond(order.taker_bond)
|
||||
order.status = Order.Status.PUB
|
||||
order.taker = None
|
||||
order.taker_bond = None
|
||||
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
||||
order.save()
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.WFI:
|
||||
# The trade could happen without a buyer invoice. However, this user
|
||||
# is most likely AFK since he did not submit an invoice; will most
|
||||
# likely desert the contract as well.
|
||||
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:
|
||||
cls.settle_bond(order.maker_bond)
|
||||
order.status = Order.Status.EXP
|
||||
order.maker = None
|
||||
order.taker = None
|
||||
order.save()
|
||||
return True
|
||||
|
||||
# If maker is seller, settle the taker's bond order goes back to public
|
||||
else:
|
||||
cls.settle_bond(order.taker_bond)
|
||||
order.status = Order.Status.PUB
|
||||
order.taker = None
|
||||
order.taker_bond = None
|
||||
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
||||
order.save()
|
||||
return True
|
||||
|
||||
elif order.status == Order.Status.CHA:
|
||||
# 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"
|
||||
# as of now, we assume this is a dispute case by default.
|
||||
cls.open_dispute(order)
|
||||
return True
|
||||
|
||||
def kick_taker(order):
|
||||
''' The taker did not lock the taker_bond. Now he has to go'''
|
||||
@ -125,10 +208,48 @@ class Logics():
|
||||
order.status = Order.Status.PUB
|
||||
order.taker = None
|
||||
order.taker_bond = None
|
||||
order.expires_at = timezone.now() + timedelta(hours=PUBLIC_ORDER_DURATION) ## TO FIX. Restore the remaining order durantion, not all of it!
|
||||
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.PUB])
|
||||
order.save()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def open_dispute(cls, order, user=None):
|
||||
|
||||
# Always settle the escrow during a dispute (same as with 'Fiat Sent')
|
||||
if not order.trade_escrow.status == LNPayment.Status.SETLED:
|
||||
cls.settle_escrow(order)
|
||||
|
||||
order.is_disputed = True
|
||||
order.status = Order.Status.DIS
|
||||
order.expires_at = order.created_at + timedelta(seconds=Order.t_to_expire[Order.Status.DIS])
|
||||
order.save()
|
||||
|
||||
# User could be None if a dispute is open automatically due to weird expiration.
|
||||
if not user == None:
|
||||
profile = user.profile
|
||||
profile.num_disputes = profile.num_disputes + 1
|
||||
profile.orders_disputes_started = list(profile.orders_disputes_started).append(str(order.id))
|
||||
profile.save()
|
||||
|
||||
return True, None
|
||||
def dispute_statement(order, user, statement):
|
||||
''' Updates the dispute statements in DB'''
|
||||
|
||||
if len(statement) > 5000:
|
||||
return False, {'bad_statement':'The statement is longer than 5000 characters'}
|
||||
if order.maker == user:
|
||||
order.maker_statement = statement
|
||||
else:
|
||||
order.taker_statement = statement
|
||||
|
||||
# If both statements are in, move to wait for dispute resolution
|
||||
if order.maker_statement != None and order.taker_statement != None:
|
||||
order.status = Order.Status.WFR
|
||||
order.expires_at = timezone.now() + Order.t_to_expire[Order.Status.WFR]
|
||||
|
||||
order.save()
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def buyer_invoice_amount(cls, order, user):
|
||||
''' Computes buyer invoice amount. Uses order.last_satoshis,
|
||||
@ -234,7 +355,7 @@ class Logics():
|
||||
on the LN node and order book. TODO Only charge a small part of the bond (requires maker submitting an invoice)'''
|
||||
elif order.status == Order.Status.PUB and order.maker == user:
|
||||
#Settle the maker bond (Maker loses the bond for cancelling public order)
|
||||
if cls.settle_maker_bond(order):
|
||||
if cls.settle_bond(order.maker_bond):
|
||||
order.maker = None
|
||||
order.status = Order.Status.UCA
|
||||
order.save()
|
||||
@ -257,7 +378,7 @@ class Logics():
|
||||
'''The order into cancelled status if maker cancels.'''
|
||||
elif order.status > Order.Status.PUB and order.status < Order.Status.CHA and order.maker == user:
|
||||
#Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||
valid = cls.settle_maker_bond(order)
|
||||
valid = cls.settle_bond(order.maker_bond)
|
||||
if valid:
|
||||
order.maker = None
|
||||
order.status = Order.Status.UCA
|
||||
@ -268,7 +389,7 @@ class Logics():
|
||||
'''The order into cancelled status if maker cancels.'''
|
||||
elif order.status > Order.Status.TAK and order.status < Order.Status.CHA and order.taker == user:
|
||||
# Settle the maker bond (Maker loses the bond for canceling an ongoing trade)
|
||||
valid = cls.settle_taker_bond(order)
|
||||
valid = cls.settle_bond(order.taker_bond)
|
||||
if valid:
|
||||
order.taker = None
|
||||
order.status = Order.Status.PUB
|
||||
@ -277,7 +398,7 @@ class Logics():
|
||||
return True, None
|
||||
|
||||
# 5) When trade collateral has been posted (after escrow)
|
||||
'''Always goes to cancelled status. Collaboration is needed.
|
||||
'''Always goes to cancelled status. Collaboration is needed.
|
||||
When a user asks for cancel, 'order.is_pending_cancel' goes True.
|
||||
When the second user asks for cancel. Order is totally cancelled.
|
||||
Has a small cost for both parties to prevent node DDOS.'''
|
||||
@ -383,7 +504,7 @@ class Logics():
|
||||
order.last_satoshis = cls.satoshis_now(order)
|
||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||
pos_text = 'Buying' if cls.is_buyer(order, user) else 'Selling'
|
||||
description = (f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + str(order.currency)}"# Order.currency_dict[str(order.currency)]}"
|
||||
description = (f"RoboSats - Taking 'Order {order.id}' {pos_text} BTC for {str(float(order.amount)) + Currency.currency_dict[str(order.currency.currency)]}"
|
||||
+ " - This is a taker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel.")
|
||||
|
||||
# Gen hold Invoice
|
||||
@ -472,22 +593,20 @@ class Logics():
|
||||
order.trade_escrow.save()
|
||||
return True
|
||||
|
||||
def settle_maker_bond(order):
|
||||
''' Settles the maker bond hold invoice'''
|
||||
def settle_bond(bond):
|
||||
''' Settles the bond hold invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.settle_hold_invoice(order.maker_bond.preimage):
|
||||
order.maker_bond.status = LNPayment.Status.SETLED
|
||||
order.maker_bond.save()
|
||||
if LNNode.settle_hold_invoice(bond.preimage):
|
||||
bond.status = LNPayment.Status.SETLED
|
||||
bond.save()
|
||||
return True
|
||||
|
||||
def settle_taker_bond(order):
|
||||
''' Settles the taker bond hold invoice'''
|
||||
# TODO ERROR HANDLING
|
||||
if LNNode.settle_hold_invoice(order.taker_bond.preimage):
|
||||
order.taker_bond.status = LNPayment.Status.SETLED
|
||||
order.taker_bond.save()
|
||||
def return_escrow(order):
|
||||
'''returns the trade escrow'''
|
||||
if LNNode.cancel_return_hold_invoice(order.trade_escrow.payment_hash):
|
||||
order.trade_escrow.status = LNPayment.Status.RETNED
|
||||
return True
|
||||
|
||||
|
||||
def return_bond(bond):
|
||||
'''returns a bond'''
|
||||
if LNNode.cancel_return_hold_invoice(bond.payment_hash):
|
||||
|
@ -107,8 +107,9 @@ class Order(models.Model):
|
||||
PAY = 13, 'Sending satoshis to buyer'
|
||||
SUC = 14, 'Sucessful trade'
|
||||
FAI = 15, 'Failed lightning network routing'
|
||||
MLD = 16, 'Maker lost dispute'
|
||||
TLD = 17, 'Taker lost dispute'
|
||||
WFR = 16, 'Wait for dispute resolution'
|
||||
MLD = 17, 'Maker lost dispute'
|
||||
TLD = 18, 'Taker lost dispute'
|
||||
|
||||
# order info
|
||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.WFB)
|
||||
@ -135,10 +136,14 @@ class Order(models.Model):
|
||||
maker = models.ForeignKey(User, related_name='maker', on_delete=models.CASCADE, null=True, default=None) # unique = True, a maker can only make one order
|
||||
taker = models.ForeignKey(User, related_name='taker', on_delete=models.SET_NULL, null=True, default=None, blank=True) # unique = True, a taker can only take one order
|
||||
is_pending_cancel = models.BooleanField(default=False, null=False) # When collaborative cancel is needed and one partner has cancelled.
|
||||
is_disputed = models.BooleanField(default=False, null=False)
|
||||
is_fiat_sent = models.BooleanField(default=False, null=False)
|
||||
|
||||
# HTLCs
|
||||
# in dispute
|
||||
is_disputed = models.BooleanField(default=False, null=False)
|
||||
maker_statement = models.TextField(max_length=5000, unique=True, null=True, default=None, blank=True)
|
||||
taker_statement = models.TextField(max_length=5000, unique=True, null=True, default=None, blank=True)
|
||||
|
||||
# LNpayments
|
||||
# Order collateral
|
||||
maker_bond = models.ForeignKey(LNPayment, related_name='maker_bond', 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)
|
||||
@ -147,11 +152,11 @@ class Order(models.Model):
|
||||
# buyer payment LN invoice
|
||||
buyer_invoice = models.ForeignKey(LNPayment, related_name='buyer_invoice', on_delete=models.SET_NULL, null=True, default=None, blank=True)
|
||||
|
||||
# cancel LN invoice // these are only needed to charge lower-than-bond amounts. E.g., a taken order has a small cost if cancelled, to avoid DDOSing.
|
||||
maker_cancel = models.ForeignKey(LNPayment, related_name='maker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True)
|
||||
taker_cancel = models.ForeignKey(LNPayment, related_name='taker_cancel', 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.
|
||||
# maker_cancel = models.ForeignKey(LNPayment, related_name='maker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True)
|
||||
# taker_cancel = models.ForeignKey(LNPayment, related_name='taker_cancel', on_delete=models.SET_NULL, null=True, default=None, blank=True)
|
||||
|
||||
total_time_to_expire = {
|
||||
t_to_expire = {
|
||||
0 : int(config('EXP_MAKER_BOND_INVOICE')) , # 'Waiting for maker bond'
|
||||
1 : 60*60*int(config('PUBLIC_ORDER_DURATION')), # 'Public'
|
||||
2 : 0, # 'Deleted'
|
||||
@ -163,13 +168,14 @@ class Order(models.Model):
|
||||
8 : 60*int(config('INVOICE_AND_ESCROW_DURATION')), # 'Waiting only for buyer invoice'
|
||||
9 : 60*60*int(config('FIAT_EXCHANGE_DURATION')), # 'Sending fiat - In chatroom'
|
||||
10 : 60*60*int(config('FIAT_EXCHANGE_DURATION')), # 'Fiat sent - In chatroom'
|
||||
11 : 24*60*60, # 'In dispute'
|
||||
11 : 10*24*60*60, # 'In dispute'
|
||||
12 : 0, # 'Collaboratively cancelled'
|
||||
13 : 24*60*60, # 'Sending satoshis to buyer'
|
||||
14 : 24*60*60, # 'Sucessful trade'
|
||||
15 : 24*60*60, # 'Failed lightning network routing'
|
||||
16 : 24*60*60, # 'Maker lost dispute'
|
||||
17 : 24*60*60, # 'Taker lost dispute'
|
||||
16 : 24*60*60, # 'Wait for dispute resolution'
|
||||
17 : 24*60*60, # 'Maker lost dispute'
|
||||
18 : 24*60*60, # 'Taker lost dispute'
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
@ -201,6 +207,8 @@ class Profile(models.Model):
|
||||
# Disputes
|
||||
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||
lost_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||
num_disputes_started = models.PositiveIntegerField(null=False, default=0)
|
||||
orders_disputes_started = models.CharField(max_length=999, null=True, default=None, validators=[validate_comma_separated_integer_list], blank=True) # Will only store ID of orders
|
||||
|
||||
# RoboHash
|
||||
avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar', blank=True)
|
||||
@ -254,7 +262,7 @@ class MarketTick(models.Model):
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, default=None, null=True, validators=[MinValueValidator(0)])
|
||||
volume = models.DecimalField(max_digits=8, decimal_places=8, default=None, null=True, validators=[MinValueValidator(0)])
|
||||
premium = models.DecimalField(max_digits=5, decimal_places=2, default=None, null=True, validators=[MinValueValidator(-100), MaxValueValidator(999)], blank=True)
|
||||
currency = models.PositiveSmallIntegerField(choices=Currency.currency_choices, null=True)
|
||||
currency = models.ForeignKey(Currency, null=True, on_delete=models.SET_NULL)
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Relevant to keep record of the historical fee, so the insight on the premium can be better analyzed
|
||||
|
@ -13,5 +13,6 @@ class MakeOrderSerializer(serializers.ModelSerializer):
|
||||
|
||||
class UpdateOrderSerializer(serializers.Serializer):
|
||||
invoice = serializers.CharField(max_length=2000, allow_null=True, allow_blank=True, default=None)
|
||||
action = serializers.ChoiceField(choices=('take','update_invoice','dispute','cancel','confirm','rate'), allow_null=False)
|
||||
statement = serializers.CharField(max_length=10000, allow_null=True, allow_blank=True, default=None)
|
||||
action = serializers.ChoiceField(choices=('take','update_invoice','submit_statement','dispute','cancel','confirm','rate'), allow_null=False)
|
||||
rating = serializers.ChoiceField(choices=('1','2','3','4','5'), allow_null=True, allow_blank=True, default=None)
|
10
api/tasks.py
10
api/tasks.py
@ -43,8 +43,8 @@ def users_cleansing():
|
||||
@shared_task(name="orders_expire")
|
||||
def orders_expire(rest_secs):
|
||||
'''
|
||||
Continuously checks order expiration times for 1 hour.
|
||||
If order is expires, it handles the actions.
|
||||
Continuously checks order expiration times for 1 hour. If order
|
||||
has expires, it calls the logics module for expiration handling.
|
||||
'''
|
||||
now = timezone.now()
|
||||
end_time = now + timedelta(hours=1)
|
||||
@ -55,8 +55,8 @@ def orders_expire(rest_secs):
|
||||
queryset = queryset.filter(expires_at__lt=now) # expires at lower than now
|
||||
|
||||
for order in queryset:
|
||||
context.append(str(order)+ " was "+ Order.Status(order.status).label)
|
||||
Logics.order_expires(order)
|
||||
if Logics.order_expires(order): # Order send to expire here
|
||||
context.append(str(order)+ " was "+ Order.Status(order.status).label)
|
||||
|
||||
# Allow for some thread rest.
|
||||
time.sleep(rest_secs)
|
||||
@ -77,12 +77,14 @@ def follow_lnd_payment():
|
||||
''' Makes a payment and follows it.
|
||||
Updates the LNpayment object, and retries
|
||||
until payment is done'''
|
||||
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def follow_lnd_hold_invoice():
|
||||
''' Follows and updates LNpayment object
|
||||
until settled or canceled'''
|
||||
|
||||
pass
|
||||
|
||||
@shared_task(name="cache_external_market_prices", ignore_result=True)
|
||||
|
13
api/views.py
13
api/views.py
@ -106,7 +106,7 @@ class OrderView(viewsets.ViewSet):
|
||||
return Response({'bad_request':'This order has been cancelled collaborativelly'},status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
data = ListOrderSerializer(order).data
|
||||
data['total_secs_exp'] = Order.total_time_to_expire[order.status]
|
||||
data['total_secs_exp'] = Order.t_to_expire[order.status]
|
||||
|
||||
# if user is under a limit (penalty), inform him.
|
||||
is_penalized, time_out = Logics.is_penalized(request.user)
|
||||
@ -217,10 +217,13 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
order = Order.objects.get(id=order_id)
|
||||
|
||||
# action is either 1)'take', 2)'confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice' 6)'rate' (counterparty)
|
||||
# action is either 1)'take', 2)'confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice'
|
||||
# 6)'submit_statement' (in dispute), 7)'rate' (counterparty)
|
||||
action = serializer.data.get('action')
|
||||
invoice = serializer.data.get('invoice')
|
||||
statement = serializer.data.get('statement')
|
||||
rating = serializer.data.get('rating')
|
||||
|
||||
|
||||
# 1) If action is take, it is a taker request!
|
||||
if action == 'take':
|
||||
@ -255,7 +258,11 @@ class OrderView(viewsets.ViewSet):
|
||||
|
||||
# 5) If action is dispute
|
||||
elif action == 'dispute':
|
||||
valid, context = Logics.open_dispute(order,request.user, rating)
|
||||
valid, context = Logics.open_dispute(order,request.user)
|
||||
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
elif action == 'submit_statement':
|
||||
valid, context = Logics.dispute_statement(order,request.user, statement)
|
||||
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 6) If action is rate
|
||||
|
@ -21,10 +21,7 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
|
||||
# if not (Logics.is_buyer(order[0], self.user) or Logics.is_seller(order[0], self.user)):
|
||||
# print ("Outta this chat")
|
||||
# return False
|
||||
|
||||
print(self.user_nick)
|
||||
print(self.order_id)
|
||||
|
||||
|
||||
await self.channel_layer.group_add(
|
||||
self.room_group_name,
|
||||
self.channel_name
|
||||
|
@ -38,6 +38,7 @@ export default class TradeBox extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
badInvoice: false,
|
||||
badStatement: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,8 +201,6 @@ export default class TradeBox extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
// Fix this. It's clunky because it takes time. this.props.data does not refresh until next refresh of OrderPage.
|
||||
|
||||
handleClickSubmitInvoiceButton=()=>{
|
||||
this.setState({badInvoice:false});
|
||||
|
||||
@ -219,10 +218,34 @@ export default class TradeBox extends Component {
|
||||
& console.log(data));
|
||||
}
|
||||
|
||||
handleInputDisputeChanged=(e)=>{
|
||||
this.setState({
|
||||
statement: e.target.value,
|
||||
badStatement: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleClickSubmitStatementButton=()=>{
|
||||
this.setState({badInvoice:false});
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
|
||||
body: JSON.stringify({
|
||||
'action':'submit_statement',
|
||||
'statement': this.state.statement,
|
||||
}),
|
||||
};
|
||||
fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => this.setState({badStatement:data.bad_statement})
|
||||
& console.log(data));
|
||||
}
|
||||
|
||||
showInputInvoice(){
|
||||
return (
|
||||
|
||||
// TODO Camera option to read QR
|
||||
// TODO Option to upload files and images
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
@ -252,7 +275,51 @@ export default class TradeBox extends Component {
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button onClick={this.handleClickSubmitInvoiceButton} variant='contained' color='primary'>Submit</Button>
|
||||
<Button onClick={this.handleClickSubmitStatementButton} variant='contained' color='primary'>Submit</Button>
|
||||
</Grid>
|
||||
|
||||
{this.showBondIsLocked()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
// Asks the user for a dispute statement.
|
||||
showInDisputeStatement(){
|
||||
return (
|
||||
|
||||
// TODO Option to upload files
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} align="center">
|
||||
<Typography color="primary" component="subtitle1" variant="subtitle1">
|
||||
<b> A dispute has been opened </b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="left">
|
||||
<Typography component="body2" variant="body2">
|
||||
Please, submit your statement. Be clear and specific about what happened and provide the necessary
|
||||
evidence. It is best to provide a burner email, XMPP or telegram username to follow up with the staff.
|
||||
Disputes are solved at the discretion of real robots <i>(aka humans)</i>, so be as helpful
|
||||
as possible to ensure a fair outcome. Max 5000 chars.
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align="center">
|
||||
<TextField
|
||||
error={this.state.badStatement}
|
||||
helperText={this.state.badStatement ? this.state.badStatement : "" }
|
||||
label={"Submit dispute statement"}
|
||||
required
|
||||
inputProps={{
|
||||
style: {textAlign:"center"}
|
||||
}}
|
||||
multiline
|
||||
rows={4}
|
||||
onChange={this.handleInputDisputeChanged}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button onClick={this.handleClickSubmitStatementButton} variant='contained' color='primary'>Submit</Button>
|
||||
</Grid>
|
||||
|
||||
{this.showBondIsLocked()}
|
||||
@ -463,8 +530,8 @@ handleRatingChange=(e)=>{
|
||||
{/* Trade Finished - Payment Routing Failed */}
|
||||
{this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""}
|
||||
|
||||
{/* Trade Finished - Payment Routing Failed - TODO Needs more planning */}
|
||||
{this.props.data.statusCode == 11 ? this.showInDispute() : ""}
|
||||
{/* Trade Finished - TODO Needs more planning */}
|
||||
{this.props.data.statusCode == 11 ? this.showInDisputeStatement() : ""}
|
||||
|
||||
|
||||
{/* TODO */}
|
||||
|
Loading…
Reference in New Issue
Block a user