Improve failure logics. Add platform rating.

This commit is contained in:
Reckless_Satoshi 2022-02-04 10:07:09 -08:00
parent 3f6731d3e0
commit 1a6aa8a9ee
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
7 changed files with 99 additions and 16 deletions

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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(

View File

@ -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)=>{
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
What do you think of <b>{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}</b>?
What do you think of <b>{this.props.data.is_maker ? this.props.data.taker_nick : this.props.data.maker_nick}</b>?
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Rating name="size-large" defaultValue={2} size="large" onChange={this.handleRatingChange} />
<Rating name="size-large" defaultValue={0} size="large" onChange={this.handleRatingUserChange} />
</Grid>
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
What do you think of 🤖<b>RoboSats</b>🤖?
</Typography>
</Grid>
<Grid item xs={12} align="center">
<Rating name="size-large" defaultValue={0} size="large" onChange={this.handleRatingRobosatsChange} />
</Grid>
{this.state.rating_platform==5 ?
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
<p>Thank you! RoboSats loves you too </p>
<p>RoboSats gets better with more liquidity and users. Tell a bitcoiner friend about Robosats!</p>
</Typography>
</Grid>
: null}
{this.state.rating_platform!=5 & this.state.rating_platform!=null ?
<Grid item xs={12} align="center">
<Typography component="body2" variant="body2" align="center">
Thank you for using Robosats! Let us know what you did not like and how the platform could improve
(<a href="https://t.me/robosats">Telegram</a> / <a href="https://github.com/Reckless-Satoshi/robosats/issues">Github</a>)
</Typography>
</Grid>
: null}
<Grid item xs={12} align="center">
<Button color='primary' onClick={() => {this.props.push('/')}}>Start Again</Button>
</Grid>
@ -698,11 +741,28 @@ handleRatingChange=(e)=>{
RoboSats is trying to pay your lightning invoice. Remember that lightning nodes must
be online in order to receive payments.
</Typography>
<br/>
<Grid item xs={12} align="center">
<CircularProgress/>
</Grid>
</Grid>
</Grid>
)
}
// Countdown Renderer callback with condition
countdownRenderer = ({ minutes, seconds, completed }) => {
if (completed) {
// Render a completed state
return (<div align="center"><span> Retrying! </span><br/><CircularProgress/></div> );
} else {
return (
<span>{zeroPad(minutes)}m {zeroPad(seconds)}s </span>
);
}
};
showRoutingFailed=()=>{
// TODO If it has failed 3 times, ask for a new invoice.
if(this.props.data.invoice_expired){