From fc4f3e159375ae451be3c722b53e85c4c399ecdb Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Fri, 28 Apr 2023 09:19:18 +0000 Subject: [PATCH] Add undo confirm fiat sent action (#461) * Add undo confirm fiat sent action * Collect phrases * Small fixes --- api/logics.py | 21 ++++++ api/models.py | 2 +- api/oas_schemas.py | 27 ++++---- api/serializers.py | 9 +-- api/views.py | 15 ++--- frontend/src/basic/OrderPage/index.tsx | 1 - .../TradeBox/Dialogs/ConfirmUndoFiatSent.tsx | 64 +++++++++++++++++++ .../src/components/TradeBox/Dialogs/index.ts | 1 + .../src/components/TradeBox/Prompts/Chat.tsx | 27 +++++++- frontend/src/components/TradeBox/index.tsx | 18 ++++++ frontend/src/models/Book.model.ts | 1 - frontend/src/models/Order.model.ts | 3 +- frontend/static/locales/ca.json | 55 ++++++++-------- frontend/static/locales/cs.json | 55 ++++++++-------- frontend/static/locales/de.json | 55 ++++++++-------- frontend/static/locales/en.json | 55 ++++++++-------- frontend/static/locales/es.json | 55 ++++++++-------- frontend/static/locales/eu.json | 55 ++++++++-------- frontend/static/locales/fr.json | 55 ++++++++-------- frontend/static/locales/it.json | 55 ++++++++-------- frontend/static/locales/pl.json | 55 ++++++++-------- frontend/static/locales/pt.json | 55 ++++++++-------- frontend/static/locales/ru.json | 55 ++++++++-------- frontend/static/locales/sv.json | 55 ++++++++-------- frontend/static/locales/th.json | 55 ++++++++-------- frontend/static/locales/zh-SI.json | 55 ++++++++-------- frontend/static/locales/zh-TR.json | 55 ++++++++-------- 27 files changed, 601 insertions(+), 413 deletions(-) create mode 100644 frontend/src/components/TradeBox/Dialogs/ConfirmUndoFiatSent.tsx diff --git a/api/logics.py b/api/logics.py index 9032a5bb..85d3b261 100644 --- a/api/logics.py +++ b/api/logics.py @@ -1589,6 +1589,27 @@ class Logics: order.save() return True, None + @classmethod + def undo_confirm_fiat_sent(cls, order, user): + """If Order is in the CHAT states: + If user is buyer: fiat_sent goes to true. + """ + if not cls.is_buyer(order, user): + return False, { + "bad_request": "Only the buyer can undo the fiat sent confirmation." + } + + if order.status != Order.Status.FSE: + return False, { + "bad_request": "Only orders in Chat and with fiat sent confirmed can be reverted." + } + order.status = Order.Status.CHA + order.is_fiat_sent = False + order.reverted_fiat_sent = True + order.save() + + return True, None + def pause_unpause_public_order(order, user): if not order.maker == user: return False, { diff --git a/api/models.py b/api/models.py index b26d6a41..50ed0d95 100644 --- a/api/models.py +++ b/api/models.py @@ -346,7 +346,6 @@ class Order(models.Model): payment_method = models.CharField( max_length=70, null=False, default="not specified", blank=True ) - bondless_taker = models.BooleanField(default=False, null=False, blank=False) # order pricing method. A explicit amount of sats, or a relative premium above/below market. is_explicit = models.BooleanField(default=False, null=False) # marked to market @@ -440,6 +439,7 @@ class Order(models.Model): taker_asked_cancel = models.BooleanField(default=False, null=False) is_fiat_sent = models.BooleanField(default=False, null=False) + reverted_fiat_sent = models.BooleanField(default=False, null=False) # in dispute is_disputed = models.BooleanField(default=False, null=False) diff --git a/api/oas_schemas.py b/api/oas_schemas.py index 4b93ba7a..c382240d 100644 --- a/api/oas_schemas.py +++ b/api/oas_schemas.py @@ -28,7 +28,6 @@ class MakerViewSchema: - `public_duration` - **{PUBLIC_DURATION}** - `escrow_duration` - **{ESCROW_DURATION}** - `bond_size` - **{BOND_SIZE}** - - `bondless_taker` - **false** - `has_range` - **false** - `premium` - **0** """ @@ -81,7 +80,6 @@ class OrderViewSchema: - `is_explicit` - `premium` - `satoshis` - - `bondless_taker` - `maker` - `taker` - `escrow_duration` @@ -272,10 +270,10 @@ class OrderViewSchema: collaborativelly cancelling orders for both parties. - `confirm` - This is a **crucial** action. This confirms the sending and - recieving of fiat depending on whether you are a buyer or + receiving of fiat depending on whether you are a buyer or seller. There is not much RoboSats can do to actually confirm and verify the fiat payment channel. It is up to you to make - sure of the correct amount was recieved before you confirm. + sure of the correct amount was received before you confirm. This action is only allowed when status is either `9` (Sending fiat - In chatroom) or `10` (Fiat sent - In chatroom) - If you are the buyer, it simply sets `fiat_sent` to `true` @@ -283,11 +281,16 @@ class OrderViewSchema: method selected by the seller and signals the seller that the fiat payment was done. - If you are the seller, be very careful and double check - before perorming this action. Check that your fiat payment - method was successful in recieving the funds and whether it + before performing this action. Check that your fiat payment + method was successful in receiving the funds and whether it was the correct amount. This action settles the escrow and pays the buyer and sets the the order status to `13` (Sending satohis to buyer) and eventually to `14` (successful trade). + - `undo_confirm` + - This action will undo the fiat_sent confirmation by the buyer + it is allowed only once the fiat is confirmed as sent and can + enable the collaborative cancellation option if an off-robosats + payment cannot be completed or is blocked. - `dispute` - This action is allowed only if status is `9` or `10`. It sets the order status to `11` (In dispute) and sets `is_disputed` to @@ -298,11 +301,11 @@ class OrderViewSchema: bond. - `submit_statement` - This action updates the dispute statement. Allowed only when - status is `11` (In dispute). `satement` must be sent in the + status is `11` (In dispute). `statement` must be sent in the request body and should be a string. 100 chars < length of - `statement` < 5000 chars. You need to discribe the reason for + `statement` < 5000 chars. You need to describe the reason for raising a dispute. The `(m|t)aker_statement` field is set - respectively. Only when both parties have submitted thier + respectively. Only when both parties have submitted their dispute statement, the order status changes to `16` (Waiting for dispute resolution) - `rate_user` @@ -789,10 +792,6 @@ class LimitViewSchema: "type": "integer", "description": "Maximum amount allowed in an order in the particular currency", }, - "max_bondless_amount": { - "type": "integer", - "description": "Maximum amount allowed in a bondless order", - }, }, }, }, @@ -806,7 +805,6 @@ class LimitViewSchema: "price": "42069.69", "min_amount": "4.2", "max_amount": "420.69", - "max_bondless_amount": "10.1", }, }, status_codes=[200], @@ -846,7 +844,6 @@ class HistoricalViewSchema: "price": "42069.69", "min_amount": "4.2", "max_amount": "420.69", - "max_bondless_amount": "10.1", }, }, status_codes=[200], diff --git a/api/serializers.py b/api/serializers.py index c1267f2d..b33d8314 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -62,7 +62,6 @@ class ListOrderSerializer(serializers.ModelSerializer): "is_explicit", "premium", "satoshis", - "bondless_taker", "maker", "taker", "escrow_duration", @@ -337,7 +336,6 @@ class OrderDetailSerializer(serializers.ModelSerializer): "is_explicit", "premium", "satoshis", - "bondless_taker", "maker", "taker", "escrow_duration", @@ -430,7 +428,6 @@ class OrderPublicSerializer(serializers.ModelSerializer): "is_explicit", "premium", "satoshis", - "bondless_taker", "maker", "maker_nick", "maker_status", @@ -460,10 +457,6 @@ class MakeOrderSerializer(serializers.ModelSerializer): default=False, help_text="Whether the order specifies a range of amount or a fixed amount.\n\nIf `true`, then `min_amount` and `max_amount` fields are **required**.\n\n If `false` then `amount` is **required**", ) - bondless_taker = serializers.BooleanField( - default=False, - help_text="Whether bondless takers are allowed for this order or not", - ) class Meta: model = Order @@ -481,7 +474,6 @@ class MakeOrderSerializer(serializers.ModelSerializer): "public_duration", "escrow_duration", "bond_size", - "bondless_taker", ) @@ -513,6 +505,7 @@ class UpdateOrderSerializer(serializers.Serializer): "dispute", "cancel", "confirm", + "undo_confirm", "rate_user", "rate_platform", ), diff --git a/api/views.py b/api/views.py index c26b36be..243f2302 100644 --- a/api/views.py +++ b/api/views.py @@ -114,7 +114,6 @@ class MakerView(CreateAPIView): public_duration = serializer.data.get("public_duration") escrow_duration = serializer.data.get("escrow_duration") bond_size = serializer.data.get("bond_size") - bondless_taker = serializer.data.get("bondless_taker") # Optional params if public_duration is None: @@ -123,8 +122,6 @@ class MakerView(CreateAPIView): escrow_duration = ESCROW_DURATION if bond_size is None: bond_size = BOND_SIZE - if bondless_taker is None: - bondless_taker = False if has_range is None: has_range = False @@ -168,7 +165,6 @@ class MakerView(CreateAPIView): public_duration=public_duration, escrow_duration=escrow_duration, bond_size=bond_size, - bondless_taker=bondless_taker, ) order.last_satoshis = order.t0_satoshis = Logics.satoshis_now(order) @@ -455,7 +451,6 @@ class OrderView(viewsets.ViewSet): ]: data["public_duration"] = order.public_duration data["bond_size"] = order.bond_size - data["bondless_taker"] = order.bondless_taker # Adds trade summary if order.status in [Order.Status.SUC, Order.Status.PAY, Order.Status.FAI]: @@ -501,7 +496,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' + # action is either 1)'take', 2)'confirm', 2.b)'undo_confirm', 3)'cancel', 4)'dispute' , 5)'update_invoice' # 5.b)'update_address' 6)'submit_statement' (in dispute), 7)'rate_user' , 8)'rate_platform' action = serializer.data.get("action") invoice = serializer.data.get("invoice") @@ -574,6 +569,12 @@ class OrderView(viewsets.ViewSet): if not valid: return Response(context, status.HTTP_400_BAD_REQUEST) + # 4.b) If action is confirm + elif action == "undo_confirm": + valid, context = Logics.undo_confirm_fiat_sent(order, request.user) + if not valid: + return Response(context, status.HTTP_400_BAD_REQUEST) + # 5) If action is dispute elif action == "dispute": valid, context = Logics.open_dispute(order, request.user) @@ -1015,7 +1016,6 @@ class LimitView(ListAPIView): # Trade limits as BTC min_trade = float(config("MIN_TRADE")) / 100000000 max_trade = float(config("MAX_TRADE")) / 100000000 - max_bondless_trade = float(config("MAX_TRADE_BONDLESS_TAKER")) / 100000000 payload = {} queryset = Currency.objects.all().order_by("currency") @@ -1028,7 +1028,6 @@ class LimitView(ListAPIView): "price": exchange_rate, "min_amount": min_trade * exchange_rate, "max_amount": max_trade * exchange_rate, - "max_bondless_amount": max_bondless_trade * exchange_rate, } return Response(payload, status.HTTP_200_OK) diff --git a/frontend/src/basic/OrderPage/index.tsx b/frontend/src/basic/OrderPage/index.tsx index a8dd3cec..31bddc73 100644 --- a/frontend/src/basic/OrderPage/index.tsx +++ b/frontend/src/basic/OrderPage/index.tsx @@ -57,7 +57,6 @@ const OrderPage = (): JSX.Element => { public_duration: order.public_duration, escrow_duration: order.escrow_duration, bond_size: order.bond_size, - bondless_taker: order.bondless_taker, }; apiClient.post(baseUrl, '/api/make/', body).then((data: any) => { if (data.bad_request) { diff --git a/frontend/src/components/TradeBox/Dialogs/ConfirmUndoFiatSent.tsx b/frontend/src/components/TradeBox/Dialogs/ConfirmUndoFiatSent.tsx new file mode 100644 index 00000000..1e9960a2 --- /dev/null +++ b/frontend/src/components/TradeBox/Dialogs/ConfirmUndoFiatSent.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogActions, + DialogContent, + DialogContentText, + Button, +} from '@mui/material'; +import { Order } from '../../../models'; +import { LoadingButton } from '@mui/lab'; + +interface ConfirmUndoFiatSentDialogProps { + open: boolean; + loadingButton: boolean; + order: Order; + onClose: () => void; + onConfirmClick: () => void; +} + +export const ConfirmUndoFiatSentDialog = ({ + open, + loadingButton, + onClose, + onConfirmClick, +}: ConfirmUndoFiatSentDialogProps): JSX.Element => { + const { t } = useTranslation(); + const [time, setTime] = useState(60); + + useEffect(() => { + if (time > 0 && open) { + setTimeout(() => setTime(time - 1), 1000); + } + }, [time, open]); + + const onClick = () => { + onConfirmClick(); + setTime(300); + }; + + return ( + + {t('Revert the confirmation of fiat sent?')} + + + {t( + 'READ. In case your payment to the seller has been blocked and it is absolutely impossible to finish the trade, you can revert your confirmation of "Fiat sent". Do so only if you and the seller have ALREADY AGREED in the chat to proceed to a collaborative cancellation. After confirming, the "Collaborative cancel" button will be visible again. Only click this button if you know what you are doing. First time users of RoboSats are highly discouraged from performing this action! Make 100% sure your payment has failed and the amount is in your account.', + )} + + + + + 0} loading={loadingButton} onClick={onClick}> + {time > 0 ? t('Wait ({{time}})', { time }) : t('Confirm')} + + + + ); +}; + +export default ConfirmUndoFiatSentDialog; diff --git a/frontend/src/components/TradeBox/Dialogs/index.ts b/frontend/src/components/TradeBox/Dialogs/index.ts index 9551b0e6..29064531 100644 --- a/frontend/src/components/TradeBox/Dialogs/index.ts +++ b/frontend/src/components/TradeBox/Dialogs/index.ts @@ -1,5 +1,6 @@ export { ConfirmDisputeDialog } from './ConfirmDispute'; export { ConfirmFiatSentDialog } from './ConfirmFiatSent'; +export { ConfirmUndoFiatSentDialog } from './ConfirmUndoFiatSent'; export { ConfirmFiatReceivedDialog } from './ConfirmFiatReceived'; export { ConfirmCancelDialog } from './ConfirmCancel'; export { ConfirmCollabCancelDialog } from './ConfirmCollabCancel'; diff --git a/frontend/src/components/TradeBox/Prompts/Chat.tsx b/frontend/src/components/TradeBox/Prompts/Chat.tsx index d3c9384f..60bac6aa 100644 --- a/frontend/src/components/TradeBox/Prompts/Chat.tsx +++ b/frontend/src/components/TradeBox/Prompts/Chat.tsx @@ -13,7 +13,9 @@ interface ChatPromptProps { order: Order; robot: Robot; onClickConfirmSent: () => void; + onClickUndoConfirmSent: () => void; loadingSent: boolean; + loadingUndoSent: boolean; onClickConfirmReceived: () => void; loadingReceived: boolean; onClickDispute: () => void; @@ -27,8 +29,10 @@ export const ChatPrompt = ({ order, robot, onClickConfirmSent, + onClickUndoConfirmSent, onClickConfirmReceived, loadingSent, + loadingUndoSent, loadingReceived, onClickDispute, loadingDispute, @@ -40,6 +44,7 @@ export const ChatPrompt = ({ const [sentButton, setSentButton] = useState(false); const [receivedButton, setReceivedButton] = useState(false); + const [undoSentButton, setUndoSentButton] = useState(false); const [enableDisputeButton, setEnableDisputeButton] = useState(false); const [enableDisputeTime, setEnableDisputeTime] = useState(new Date(order.expires_at)); const [text, setText] = useState(''); @@ -56,10 +61,10 @@ export const ChatPrompt = ({ }; useEffect(() => { - // open dispute button enables 12h before expiry + // open dispute button enables 18h before expiry const now = Date.now(); const expires_at = new Date(order.expires_at); - expires_at.setHours(expires_at.getHours() - 12); + expires_at.setHours(expires_at.getHours() - 18); setEnableDisputeButton(now > expires_at); setEnableDisputeTime(expires_at); @@ -68,6 +73,7 @@ export const ChatPrompt = ({ if (order.is_buyer) { setSentButton(true); setReceivedButton(false); + setUndoSentButton(false); setText( t( "Say hi! Ask for payment details and click 'Confirm Sent' as soon as the payment is sent.", @@ -75,6 +81,7 @@ export const ChatPrompt = ({ ); } else { setSentButton(false); + setUndoSentButton(false); setReceivedButton(false); setText( t( @@ -90,10 +97,12 @@ export const ChatPrompt = ({ // Fiat has been sent already if (order.is_buyer) { setSentButton(false); + setUndoSentButton(true); setReceivedButton(false); setText(t('Wait for the seller to confirm he has received the payment.')); } else { setSentButton(false); + setUndoSentButton(false); setReceivedButton(true); setText(t("The buyer has sent the fiat. Click 'Confirm Received' once you receive it.")); } @@ -170,6 +179,20 @@ export const ChatPrompt = ({ ) : ( <> )} + {undoSentButton ? ( + + + {t('Payment failed?')} + + + ) : ( + <> + )} {receivedButton ? ( setOpen({ ...open, confirmFiatSent: true })} + onClickUndoConfirmSent={() => setOpen({ ...open, confirmUndoFiatSent: true })} onClickConfirmReceived={() => setOpen({ ...open, confirmFiatReceived: true })} loadingSent={loadingButtons.fiatSent} + loadingUndoSent={loadingButtons.undoFiatSent} loadingReceived={loadingButtons.fiatReceived} onClickDispute={() => setOpen({ ...open, confirmDispute: true })} loadingDispute={loadingButtons.openDispute} @@ -681,6 +692,13 @@ const TradeBox = ({ onClose={() => setOpen(closeAll)} onConfirmClick={confirmFiatSent} /> + setOpen(closeAll)} + onConfirmClick={confirmUndoFiatSent} + />