mirror of
https://github.com/RoboSats/robosats.git
synced 2025-02-22 13:19:02 +00:00
Add expiration logics. Add dispute statements.
This commit is contained in:
parent
9009f35269
commit
9d883ccc4d
159
api/logics.py
159
api/logics.py
@ -97,21 +97,104 @@ class Logics():
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def order_expires(cls, order):
|
def order_expires(cls, order):
|
||||||
''' General case when time runs out. Only
|
''' General cases when time runs out.'''
|
||||||
used when the maker does not lock a publishing bond'''
|
|
||||||
|
|
||||||
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.status = Order.Status.EXP
|
||||||
order.maker = None
|
order.maker = None
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.save()
|
order.save()
|
||||||
|
return True
|
||||||
|
|
||||||
if order.status == Order.Status.PUB:
|
elif order.status == Order.Status.PUB:
|
||||||
cls.return_bond(order.maker_bond)
|
cls.return_bond(order.maker_bond)
|
||||||
order.status = Order.Status.EXP
|
order.status = Order.Status.EXP
|
||||||
order.maker = None
|
order.maker = None
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.save()
|
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):
|
def kick_taker(order):
|
||||||
''' The taker did not lock the taker_bond. Now he has to go'''
|
''' 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.status = Order.Status.PUB
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.taker_bond = 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()
|
order.save()
|
||||||
return True
|
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
|
@classmethod
|
||||||
def buyer_invoice_amount(cls, order, user):
|
def buyer_invoice_amount(cls, order, user):
|
||||||
''' Computes buyer invoice amount. Uses order.last_satoshis,
|
''' 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)'''
|
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:
|
elif order.status == Order.Status.PUB and order.maker == user:
|
||||||
#Settle the maker bond (Maker loses the bond for cancelling public order)
|
#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.maker = None
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
order.save()
|
order.save()
|
||||||
@ -257,7 +378,7 @@ class Logics():
|
|||||||
'''The order into cancelled status if maker cancels.'''
|
'''The order into cancelled status if maker cancels.'''
|
||||||
elif order.status > Order.Status.PUB and order.status < Order.Status.CHA and order.maker == user:
|
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)
|
#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:
|
if valid:
|
||||||
order.maker = None
|
order.maker = None
|
||||||
order.status = Order.Status.UCA
|
order.status = Order.Status.UCA
|
||||||
@ -268,7 +389,7 @@ class Logics():
|
|||||||
'''The order into cancelled status if maker cancels.'''
|
'''The order into cancelled status if maker cancels.'''
|
||||||
elif order.status > Order.Status.TAK and order.status < Order.Status.CHA and order.taker == user:
|
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)
|
# 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:
|
if valid:
|
||||||
order.taker = None
|
order.taker = None
|
||||||
order.status = Order.Status.PUB
|
order.status = Order.Status.PUB
|
||||||
@ -383,7 +504,7 @@ class Logics():
|
|||||||
order.last_satoshis = cls.satoshis_now(order)
|
order.last_satoshis = cls.satoshis_now(order)
|
||||||
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
|
||||||
pos_text = 'Buying' if cls.is_buyer(order, user) else 'Selling'
|
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.")
|
+ " - 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
|
# Gen hold Invoice
|
||||||
@ -472,20 +593,18 @@ class Logics():
|
|||||||
order.trade_escrow.save()
|
order.trade_escrow.save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def settle_maker_bond(order):
|
def settle_bond(bond):
|
||||||
''' Settles the maker bond hold invoice'''
|
''' Settles the bond hold invoice'''
|
||||||
# TODO ERROR HANDLING
|
# TODO ERROR HANDLING
|
||||||
if LNNode.settle_hold_invoice(order.maker_bond.preimage):
|
if LNNode.settle_hold_invoice(bond.preimage):
|
||||||
order.maker_bond.status = LNPayment.Status.SETLED
|
bond.status = LNPayment.Status.SETLED
|
||||||
order.maker_bond.save()
|
bond.save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def settle_taker_bond(order):
|
def return_escrow(order):
|
||||||
''' Settles the taker bond hold invoice'''
|
'''returns the trade escrow'''
|
||||||
# TODO ERROR HANDLING
|
if LNNode.cancel_return_hold_invoice(order.trade_escrow.payment_hash):
|
||||||
if LNNode.settle_hold_invoice(order.taker_bond.preimage):
|
order.trade_escrow.status = LNPayment.Status.RETNED
|
||||||
order.taker_bond.status = LNPayment.Status.SETLED
|
|
||||||
order.taker_bond.save()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_bond(bond):
|
def return_bond(bond):
|
||||||
|
@ -107,8 +107,9 @@ class Order(models.Model):
|
|||||||
PAY = 13, 'Sending satoshis to buyer'
|
PAY = 13, 'Sending satoshis to buyer'
|
||||||
SUC = 14, 'Sucessful trade'
|
SUC = 14, 'Sucessful trade'
|
||||||
FAI = 15, 'Failed lightning network routing'
|
FAI = 15, 'Failed lightning network routing'
|
||||||
MLD = 16, 'Maker lost dispute'
|
WFR = 16, 'Wait for dispute resolution'
|
||||||
TLD = 17, 'Taker lost dispute'
|
MLD = 17, 'Maker lost dispute'
|
||||||
|
TLD = 18, 'Taker lost dispute'
|
||||||
|
|
||||||
# order info
|
# order info
|
||||||
status = models.PositiveSmallIntegerField(choices=Status.choices, null=False, default=Status.WFB)
|
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
|
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
|
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_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)
|
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
|
# 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.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)
|
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 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)
|
||||||
|
|
||||||
# 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.
|
# 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)
|
# 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)
|
# 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'
|
0 : int(config('EXP_MAKER_BOND_INVOICE')) , # 'Waiting for maker bond'
|
||||||
1 : 60*60*int(config('PUBLIC_ORDER_DURATION')), # 'Public'
|
1 : 60*60*int(config('PUBLIC_ORDER_DURATION')), # 'Public'
|
||||||
2 : 0, # 'Deleted'
|
2 : 0, # 'Deleted'
|
||||||
@ -163,13 +168,14 @@ class Order(models.Model):
|
|||||||
8 : 60*int(config('INVOICE_AND_ESCROW_DURATION')), # 'Waiting only for buyer invoice'
|
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'
|
9 : 60*60*int(config('FIAT_EXCHANGE_DURATION')), # 'Sending fiat - In chatroom'
|
||||||
10 : 60*60*int(config('FIAT_EXCHANGE_DURATION')), # 'Fiat sent - 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'
|
12 : 0, # 'Collaboratively cancelled'
|
||||||
13 : 24*60*60, # 'Sending satoshis to buyer'
|
13 : 24*60*60, # 'Sending satoshis to buyer'
|
||||||
14 : 24*60*60, # 'Sucessful trade'
|
14 : 24*60*60, # 'Sucessful trade'
|
||||||
15 : 24*60*60, # 'Failed lightning network routing'
|
15 : 24*60*60, # 'Failed lightning network routing'
|
||||||
16 : 24*60*60, # 'Maker lost dispute'
|
16 : 24*60*60, # 'Wait for dispute resolution'
|
||||||
17 : 24*60*60, # 'Taker lost dispute'
|
17 : 24*60*60, # 'Maker lost dispute'
|
||||||
|
18 : 24*60*60, # 'Taker lost dispute'
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -201,6 +207,8 @@ class Profile(models.Model):
|
|||||||
# Disputes
|
# Disputes
|
||||||
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
num_disputes = models.PositiveIntegerField(null=False, default=0)
|
||||||
lost_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
|
# RoboHash
|
||||||
avatar = models.ImageField(default="static/assets/misc/unknown_avatar.png", verbose_name='Avatar', blank=True)
|
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)])
|
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)])
|
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)
|
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)
|
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
|
# 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):
|
class UpdateOrderSerializer(serializers.Serializer):
|
||||||
invoice = serializers.CharField(max_length=2000, allow_null=True, allow_blank=True, default=None)
|
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)
|
rating = serializers.ChoiceField(choices=('1','2','3','4','5'), allow_null=True, allow_blank=True, default=None)
|
@ -43,8 +43,8 @@ def users_cleansing():
|
|||||||
@shared_task(name="orders_expire")
|
@shared_task(name="orders_expire")
|
||||||
def orders_expire(rest_secs):
|
def orders_expire(rest_secs):
|
||||||
'''
|
'''
|
||||||
Continuously checks order expiration times for 1 hour.
|
Continuously checks order expiration times for 1 hour. If order
|
||||||
If order is expires, it handles the actions.
|
has expires, it calls the logics module for expiration handling.
|
||||||
'''
|
'''
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
end_time = now + timedelta(hours=1)
|
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
|
queryset = queryset.filter(expires_at__lt=now) # expires at lower than now
|
||||||
|
|
||||||
for order in queryset:
|
for order in queryset:
|
||||||
|
if Logics.order_expires(order): # Order send to expire here
|
||||||
context.append(str(order)+ " was "+ Order.Status(order.status).label)
|
context.append(str(order)+ " was "+ Order.Status(order.status).label)
|
||||||
Logics.order_expires(order)
|
|
||||||
|
|
||||||
# Allow for some thread rest.
|
# Allow for some thread rest.
|
||||||
time.sleep(rest_secs)
|
time.sleep(rest_secs)
|
||||||
@ -77,12 +77,14 @@ def follow_lnd_payment():
|
|||||||
''' Makes a payment and follows it.
|
''' Makes a payment and follows it.
|
||||||
Updates the LNpayment object, and retries
|
Updates the LNpayment object, and retries
|
||||||
until payment is done'''
|
until payment is done'''
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def follow_lnd_hold_invoice():
|
def follow_lnd_hold_invoice():
|
||||||
''' Follows and updates LNpayment object
|
''' Follows and updates LNpayment object
|
||||||
until settled or canceled'''
|
until settled or canceled'''
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@shared_task(name="cache_external_market_prices", ignore_result=True)
|
@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)
|
return Response({'bad_request':'This order has been cancelled collaborativelly'},status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
data = ListOrderSerializer(order).data
|
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.
|
# if user is under a limit (penalty), inform him.
|
||||||
is_penalized, time_out = Logics.is_penalized(request.user)
|
is_penalized, time_out = Logics.is_penalized(request.user)
|
||||||
@ -217,11 +217,14 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
order = Order.objects.get(id=order_id)
|
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')
|
action = serializer.data.get('action')
|
||||||
invoice = serializer.data.get('invoice')
|
invoice = serializer.data.get('invoice')
|
||||||
|
statement = serializer.data.get('statement')
|
||||||
rating = serializer.data.get('rating')
|
rating = serializer.data.get('rating')
|
||||||
|
|
||||||
|
|
||||||
# 1) If action is take, it is a taker request!
|
# 1) If action is take, it is a taker request!
|
||||||
if action == 'take':
|
if action == 'take':
|
||||||
if order.status == Order.Status.PUB:
|
if order.status == Order.Status.PUB:
|
||||||
@ -255,7 +258,11 @@ class OrderView(viewsets.ViewSet):
|
|||||||
|
|
||||||
# 5) If action is dispute
|
# 5) If action is dispute
|
||||||
elif action == '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)
|
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# 6) If action is rate
|
# 6) If action is rate
|
||||||
|
@ -22,9 +22,6 @@ class ChatRoomConsumer(AsyncWebsocketConsumer):
|
|||||||
# print ("Outta this chat")
|
# print ("Outta this chat")
|
||||||
# return False
|
# return False
|
||||||
|
|
||||||
print(self.user_nick)
|
|
||||||
print(self.order_id)
|
|
||||||
|
|
||||||
await self.channel_layer.group_add(
|
await self.channel_layer.group_add(
|
||||||
self.room_group_name,
|
self.room_group_name,
|
||||||
self.channel_name
|
self.channel_name
|
||||||
|
@ -38,6 +38,7 @@ export default class TradeBox extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
badInvoice: false,
|
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=()=>{
|
handleClickSubmitInvoiceButton=()=>{
|
||||||
this.setState({badInvoice:false});
|
this.setState({badInvoice:false});
|
||||||
|
|
||||||
@ -219,10 +218,34 @@ export default class TradeBox extends Component {
|
|||||||
& console.log(data));
|
& 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(){
|
showInputInvoice(){
|
||||||
return (
|
return (
|
||||||
|
|
||||||
// TODO Camera option to read QR
|
// TODO Option to upload files and images
|
||||||
|
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
@ -252,7 +275,51 @@ export default class TradeBox extends Component {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} align="center">
|
<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>
|
</Grid>
|
||||||
|
|
||||||
{this.showBondIsLocked()}
|
{this.showBondIsLocked()}
|
||||||
@ -463,8 +530,8 @@ handleRatingChange=(e)=>{
|
|||||||
{/* Trade Finished - Payment Routing Failed */}
|
{/* Trade Finished - Payment Routing Failed */}
|
||||||
{this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""}
|
{this.props.data.isBuyer & this.props.data.statusCode == 15 ? this.showUpdateInvoice() : ""}
|
||||||
|
|
||||||
{/* Trade Finished - Payment Routing Failed - TODO Needs more planning */}
|
{/* Trade Finished - TODO Needs more planning */}
|
||||||
{this.props.data.statusCode == 11 ? this.showInDispute() : ""}
|
{this.props.data.statusCode == 11 ? this.showInDisputeStatement() : ""}
|
||||||
|
|
||||||
|
|
||||||
{/* TODO */}
|
{/* TODO */}
|
||||||
|
Loading…
Reference in New Issue
Block a user