diff --git a/api/admin.py b/api/admin.py
index 0e0d1793..5f99723f 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -41,7 +41,7 @@ class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@admin.register(Profile)
class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
- list_display = ('avatar_tag','id','user_link','total_contracts','total_ratings','avg_rating','num_disputes','lost_disputes')
+ list_display = ('avatar_tag','id','user_link','total_contracts','platform_rating','total_ratings','avg_rating','num_disputes','lost_disputes')
list_display_links = ('avatar_tag','id')
change_links =['user']
readonly_fields = ['avatar_tag']
diff --git a/api/logics.py b/api/logics.py
index f88ce41d..c71f0c5d 100644
--- a/api/logics.py
+++ b/api/logics.py
@@ -302,7 +302,7 @@ class Logics():
return False, {'bad_request':'Only the buyer of this order can provide a buyer invoice.'}
if not order.taker_bond:
return False, {'bad_request':'Wait for your order to be taken.'}
- if not (order.taker_bond.status == order.maker_bond.status == LNPayment.Status.LOCKED):
+ if not (order.taker_bond.status == order.maker_bond.status == LNPayment.Status.LOCKED) and not order.status == Order.Status.FAI:
return False, {'bad_request':'You cannot submit a invoice while bonds are not locked.'}
num_satoshis = cls.payout_amount(order, user)[1]['invoice_amount']
@@ -347,9 +347,12 @@ class Logics():
# If the order status is 'Failed Routing'. Retry payment.
if order.status == Order.Status.FAI:
- # Double check the escrow is settled.
if LNNode.double_check_htlc_is_settled(order.trade_escrow.payment_hash):
- follow_send_payment(order.payout)
+ order.status = Order.Status.PAY
+ order.payout.status = LNPayment.Status.FLIGHT
+ order.payout.routing_attempts = 0
+ order.payout.save()
+ order.save()
order.save()
return True, None
@@ -526,7 +529,7 @@ class Logics():
order.last_satoshis = cls.satoshis_now(order)
bond_satoshis = int(order.last_satoshis * BOND_SIZE)
- description = f"RoboSats - Publishing '{str(order)}' - This is a maker bond, it will freeze in your wallet temporarily and automatically return. It will be charged if you cheat or cancel."
+ description = f"RoboSats - Publishing '{str(order)}' - Maker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally."
# Gen hold Invoice
hold_payment = LNNode.gen_hold_invoice(bond_satoshis,
@@ -607,7 +610,7 @@ class Logics():
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)) + 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.")
+ + " - Taker bond - This payment WILL FREEZE IN YOUR WALLET, check on the website if it was successful. It will automatically return unless you cheat or cancel unilaterally.")
# Gen hold Invoice
hold_payment = LNNode.gen_hold_invoice(bond_satoshis,
@@ -672,7 +675,7 @@ class Logics():
# If there was no taker_bond object yet, generate one
escrow_satoshis = order.last_satoshis # Amount was fixed when taker bond was locked
- description = f"RoboSats - Escrow amount for '{str(order)}' - The escrow will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
+ description = f"RoboSats - Escrow amount for '{str(order)}' - It WILL FREEZE IN YOUR WALLET. It will be released to the buyer once you confirm you received the fiat. It will automatically return if buyer does not confirm the payment."
# Gen hold Invoice
hold_payment = LNNode.gen_hold_invoice(escrow_satoshis,
@@ -834,4 +837,10 @@ class Logics():
else:
return False, {'bad_request':'You cannot rate your counterparty yet.'}
+ return True, None
+
+ @classmethod
+ def rate_platform(cls, user, rating):
+ user.profile.platform_rating = rating
+ user.profile.save()
return True, None
\ No newline at end of file
diff --git a/api/management/commands/follow_invoices.py b/api/management/commands/follow_invoices.py
index 2a7e1abf..0fb8025a 100644
--- a/api/management/commands/follow_invoices.py
+++ b/api/management/commands/follow_invoices.py
@@ -137,6 +137,10 @@ class Command(BaseCommand):
for lnpayment in queryset:
success, _ = follow_send_payment(lnpayment) # Do follow_send_payment.delay() for further concurrency.
+ # If failed, reset mision control. (This won't scale well, just a temporary fix)
+ if not success:
+ LNNode.resetmc()
+
# If already 3 attempts and last failed. Make it expire (ask for a new invoice) an reset attempts.
if not success and lnpayment.routing_attempts == 3:
lnpayment.status = LNPayment.Status.EXPIRE
diff --git a/api/models.py b/api/models.py
index e45707e6..aff5e9ec 100644
--- a/api/models.py
+++ b/api/models.py
@@ -169,6 +169,8 @@ class Order(models.Model):
# ratings
maker_rated = models.BooleanField(default=False, null=False)
taker_rated = models.BooleanField(default=False, null=False)
+ maker_platform_rated = models.BooleanField(default=False, null=False)
+ taker_platform_rated = models.BooleanField(default=False, null=False)
t_to_expire = {
0 : int(config('EXP_MAKER_BOND_INVOICE')) , # 'Waiting for maker bond'
@@ -230,6 +232,9 @@ class Profile(models.Model):
# Penalty expiration (only used then taking/cancelling repeatedly orders in the book before comitting bond)
penalty_expiration = models.DateTimeField(null=True,default=None, blank=True)
+ # Platform rate
+ platform_rating = models.PositiveIntegerField(null=True, default=None, blank=True)
+
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
diff --git a/api/serializers.py b/api/serializers.py
index 88997f3d..aa569c26 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -14,5 +14,5 @@ class MakeOrderSerializer(serializers.ModelSerializer):
class UpdateOrderSerializer(serializers.Serializer):
invoice = serializers.CharField(max_length=2000, allow_null=True, allow_blank=True, default=None)
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)
+ action = serializers.ChoiceField(choices=('take','update_invoice','submit_statement','dispute','cancel','confirm','rate_user','rate_platform'), allow_null=False)
rating = serializers.ChoiceField(choices=('1','2','3','4','5'), allow_null=True, allow_blank=True, default=None)
\ No newline at end of file
diff --git a/api/views.py b/api/views.py
index b0b8e1d2..2329707f 100644
--- a/api/views.py
+++ b/api/views.py
@@ -275,7 +275,7 @@ 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)'submit_statement' (in dispute), 7)'rate' (counterparty)
+ # 6)'submit_statement' (in dispute), 7)'rate_user' , 'rate_platform'
action = serializer.data.get('action')
invoice = serializer.data.get('invoice')
statement = serializer.data.get('statement')
@@ -323,10 +323,15 @@ class OrderView(viewsets.ViewSet):
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
# 6) If action is rate
- elif action == 'rate' and rating:
+ elif action == 'rate_user' and rating:
valid, context = Logics.rate_counterparty(order,request.user, rating)
if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
+ # 6) If action is rate_platform
+ elif action == 'rate_platform' and rating:
+ valid, context = Logics.rate_platform(request.user, rating)
+ if not valid: return Response(context, status.HTTP_400_BAD_REQUEST)
+
# If nothing of the above... something else is going on. Probably not allowed!
else:
return Response(
diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js
index 074ad77b..59048557 100644
--- a/frontend/src/components/TradeBox.js
+++ b/frontend/src/components/TradeBox.js
@@ -1,7 +1,7 @@
import React, { Component } from "react";
-import { IconButton, Paper, Rating, Button, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
+import { IconButton, Paper, Rating, Button, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material"
import QRCode from "react-qr-code";
-import Countdown from 'react-countdown';
+import Countdown, { zeroPad} from 'react-countdown';
import Chat from "./Chat"
import MediaQuery from 'react-responsive'
import QrReader from 'react-qr-reader'
@@ -549,12 +549,31 @@ export default class TradeBox extends Component {
.then((data) => this.props.completeSetState(data));
}
-handleRatingChange=(e)=>{
+handleRatingUserChange=(e)=>{
const requestOptions = {
method: 'POST',
headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
body: JSON.stringify({
- 'action': "rate",
+ 'action': "rate_user",
+ 'rating': e.target.value,
+ }),
+ };
+ fetch('/api/order/' + '?order_id=' + this.props.data.id, requestOptions)
+ .then((response) => response.json())
+ .then((data) => this.props.completeSetState(data));
+}
+
+handleRatingRobosatsChange=(e)=>{
+ if (this.state.rating_platform != null){
+ return null
+ }
+ this.setState({rating_platform:e.target.value});
+
+ const requestOptions = {
+ method: 'POST',
+ headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),},
+ body: JSON.stringify({
+ 'action': "rate_platform",
'rating': e.target.value,
}),
};
@@ -672,12 +691,36 @@ handleRatingChange=(e)=>{
Thank you! RoboSats loves you too ❤️ RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!
+