mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 12:11:35 +00:00
Improve failure logics. Add platform rating.
This commit is contained in:
parent
3f6731d3e0
commit
1a6aa8a9ee
@ -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']
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
@ -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(
|
||||
|
@ -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){
|
||||
|
Loading…
Reference in New Issue
Block a user