mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Delay payout if buyers account got flagged as risky by filter data
Signed-off-by: HenrikJannsen <boilingfrog@gmx.com>
This commit is contained in:
parent
3a25eef64a
commit
01bd8bb86e
@ -912,6 +912,7 @@ portfolio.pending.step3_seller.xmrTxHash=Transaction ID
|
|||||||
portfolio.pending.step3_seller.xmrTxKey=Transaction key
|
portfolio.pending.step3_seller.xmrTxKey=Transaction key
|
||||||
portfolio.pending.step3_seller.buyersAccount=Buyers account data
|
portfolio.pending.step3_seller.buyersAccount=Buyers account data
|
||||||
portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt
|
portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt
|
||||||
|
portfolio.pending.step3_seller.releaseBitcoin=Release Bitcoin
|
||||||
portfolio.pending.step3_seller.showBsqWallet=Show payment in BSQ wallet
|
portfolio.pending.step3_seller.showBsqWallet=Show payment in BSQ wallet
|
||||||
portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1}
|
portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1}
|
||||||
portfolio.pending.step3_seller.buyerStartedPayment.altcoin=Check for blockchain confirmations at your altcoin wallet or block explorer and confirm the payment when you have sufficient blockchain confirmations.
|
portfolio.pending.step3_seller.buyerStartedPayment.altcoin=Check for blockchain confirmations at your altcoin wallet or block explorer and confirm the payment when you have sufficient blockchain confirmations.
|
||||||
@ -922,6 +923,12 @@ portfolio.pending.step3_seller.warn.part2=You still have not confirmed the recei
|
|||||||
Please check {0} if you have received the payment.
|
Please check {0} if you have received the payment.
|
||||||
portfolio.pending.step3_seller.openForDispute=You have not confirmed the receipt of the payment!\n\
|
portfolio.pending.step3_seller.openForDispute=You have not confirmed the receipt of the payment!\n\
|
||||||
The max. period for the trade has elapsed.\nPlease confirm or request assistance from the mediator.
|
The max. period for the trade has elapsed.\nPlease confirm or request assistance from the mediator.
|
||||||
|
|
||||||
|
portfolio.pending.step3_seller.delayedPayout=Trade with ID ''{0}'' has been flagged as high risk.\n\n\
|
||||||
|
You need to wait releasing the Bitcoin to the buyer until {1} to reduce chargeback risks.\n\n\
|
||||||
|
Please note, that this does not mean that the buyer''s account got flagged but maybe their bank or the payment method was used for chargeback fraud in the past.\n\n\
|
||||||
|
You can also request mediation and tell the mediator that this trade got flagged as high risk. The mediator can try to verify the account ownership of the buyer and if nothing is suspicious suggest an earlier payout.
|
||||||
|
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
portfolio.pending.step3_seller.onPaymentReceived.part1=Have you received the {0} payment from your trading partner?\n\n
|
portfolio.pending.step3_seller.onPaymentReceived.part1=Have you received the {0} payment from your trading partner?\n\n
|
||||||
# suppress inspection "TrailingSpacesInProperty"
|
# suppress inspection "TrailingSpacesInProperty"
|
||||||
@ -3033,6 +3040,7 @@ filterWindow.offers=Filtered offers (comma sep.)
|
|||||||
filterWindow.onions=Banned from trading addresses (comma sep.)
|
filterWindow.onions=Banned from trading addresses (comma sep.)
|
||||||
filterWindow.bannedFromNetwork=Banned from network addresses (comma sep.)
|
filterWindow.bannedFromNetwork=Banned from network addresses (comma sep.)
|
||||||
filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value]
|
filterWindow.accounts=Filtered trading account data:\nFormat: comma sep. list of [payment method id | data field | value]
|
||||||
|
filterWindow.delayedPayout=Require delayed payout:\nFormat: comma sep. list of [payment method id | data field | value]
|
||||||
filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
|
filterWindow.bannedCurrencies=Filtered currency codes (comma sep.)
|
||||||
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
|
filterWindow.bannedPaymentMethods=Filtered payment method IDs (comma sep.)
|
||||||
filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys)
|
filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys)
|
||||||
|
@ -32,6 +32,7 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
|||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.dao.DaoFacade;
|
import bisq.core.dao.DaoFacade;
|
||||||
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferDirection;
|
import bisq.core.offer.OfferDirection;
|
||||||
@ -62,6 +63,7 @@ import bisq.core.util.coin.CoinFormatter;
|
|||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
|
import bisq.common.app.DevEnv;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.FaultHandler;
|
import bisq.common.handlers.FaultHandler;
|
||||||
@ -88,6 +90,8 @@ import javafx.collections.ObservableList;
|
|||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -113,6 +117,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||||||
public final WalletPasswordWindow walletPasswordWindow;
|
public final WalletPasswordWindow walletPasswordWindow;
|
||||||
private final NotificationCenter notificationCenter;
|
private final NotificationCenter notificationCenter;
|
||||||
private final OfferUtil offerUtil;
|
private final OfferUtil offerUtil;
|
||||||
|
private final FilterManager filterManager;
|
||||||
private final CoinFormatter btcFormatter;
|
private final CoinFormatter btcFormatter;
|
||||||
|
|
||||||
final ObservableList<PendingTradesListItem> list = FXCollections.observableArrayList();
|
final ObservableList<PendingTradesListItem> list = FXCollections.observableArrayList();
|
||||||
@ -151,6 +156,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||||||
WalletPasswordWindow walletPasswordWindow,
|
WalletPasswordWindow walletPasswordWindow,
|
||||||
NotificationCenter notificationCenter,
|
NotificationCenter notificationCenter,
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
|
FilterManager filterManager,
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter) {
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
@ -167,6 +173,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||||||
this.walletPasswordWindow = walletPasswordWindow;
|
this.walletPasswordWindow = walletPasswordWindow;
|
||||||
this.notificationCenter = notificationCenter;
|
this.notificationCenter = notificationCenter;
|
||||||
this.offerUtil = offerUtil;
|
this.offerUtil = offerUtil;
|
||||||
|
this.filterManager = filterManager;
|
||||||
this.btcFormatter = formatter;
|
this.btcFormatter = formatter;
|
||||||
|
|
||||||
tradesListChangeListener = change -> onListChanged();
|
tradesListChangeListener = change -> onListChanged();
|
||||||
@ -695,5 +702,33 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean requiresPayoutDelay() {
|
||||||
|
return filterManager.isDelayedPayoutPaymentAccount(Objects.requireNonNull(getTrade()).getProcessModel().getTradePeer().getPaymentAccountPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requiredPayoutDelayHasPassed() {
|
||||||
|
return getSellerConfirmedPaymentReceiptDate() > 0 && new Date().after(getDelayedPayoutDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSellerConfirmedPaymentReceiptDate() {
|
||||||
|
if (getSellerConfirmedPaymentReceiptDate() == 0) {
|
||||||
|
Objects.requireNonNull(getTrade()).setSellerConfirmedPaymentReceiptDate(new Date().getTime());
|
||||||
|
tradeManager.requestPersistence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSellerConfirmedPaymentReceiptDate() {
|
||||||
|
return Objects.requireNonNull(getTrade()).getSellerConfirmedPaymentReceiptDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDelayedPayoutDate() {
|
||||||
|
return new Date(Objects.requireNonNull(getTrade()).getSellerConfirmedPaymentReceiptDate() + getPayoutDelay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getPayoutDelay() {
|
||||||
|
return DevEnv.isDevMode() ? TimeUnit.SECONDS.toMillis(10) :
|
||||||
|
TimeUnit.DAYS.toMillis(14);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import bisq.desktop.main.dao.wallet.tx.BsqTxView;
|
|||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
|
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
|
||||||
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
|
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
|
||||||
|
import bisq.desktop.util.DisplayUtils;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
@ -74,7 +75,9 @@ import org.fxmisc.easybind.Subscription;
|
|||||||
|
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -98,6 +101,8 @@ public class SellerStep3View extends TradeStepView {
|
|||||||
private TxConfidenceIndicator assetTxConfidenceIndicator;
|
private TxConfidenceIndicator assetTxConfidenceIndicator;
|
||||||
@Nullable
|
@Nullable
|
||||||
private ChangeListener<Number> proofResultListener;
|
private ChangeListener<Number> proofResultListener;
|
||||||
|
@Nullable
|
||||||
|
private Timer payoutDelayTimer;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -169,6 +174,8 @@ public class SellerStep3View extends TradeStepView {
|
|||||||
|
|
||||||
applyAssetTxProofResult(trade.getAssetTxProofResult());
|
applyAssetTxProofResult(trade.getAssetTxProofResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateConfirmButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -189,6 +196,11 @@ public class SellerStep3View extends TradeStepView {
|
|||||||
if (isXmrTrade()) {
|
if (isXmrTrade()) {
|
||||||
trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener);
|
trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payoutDelayTimer != null) {
|
||||||
|
payoutDelayTimer.stop();
|
||||||
|
payoutDelayTimer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -363,7 +375,7 @@ public class SellerStep3View extends TradeStepView {
|
|||||||
protected void updateDisputeState(Trade.DisputeState disputeState) {
|
protected void updateDisputeState(Trade.DisputeState disputeState) {
|
||||||
super.updateDisputeState(disputeState);
|
super.updateDisputeState(disputeState);
|
||||||
|
|
||||||
confirmButton.setDisable(!trade.confirmPermitted());
|
updateConfirmButtonDisableState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -475,14 +487,60 @@ public class SellerStep3View extends TradeStepView {
|
|||||||
|
|
||||||
private void confirmPaymentReceived() {
|
private void confirmPaymentReceived() {
|
||||||
log.info("User pressed the [Confirm payment receipt] button for Trade {}", trade.getShortId());
|
log.info("User pressed the [Confirm payment receipt] button for Trade {}", trade.getShortId());
|
||||||
busyAnimation.play();
|
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
|
||||||
|
|
||||||
model.dataModel.onFiatPaymentReceived(() -> {
|
model.dataModel.setSellerConfirmedPaymentReceiptDate();
|
||||||
}, errorMessage -> {
|
updateConfirmButton();
|
||||||
busyAnimation.stop();
|
if (!model.dataModel.requiresPayoutDelay() || model.dataModel.requiredPayoutDelayHasPassed()) {
|
||||||
new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show();
|
onReleaseBitcoin();
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfirmButton() {
|
||||||
|
if (model.dataModel.requiresPayoutDelay() &&
|
||||||
|
model.dataModel.getSellerConfirmedPaymentReceiptDate() > 0) {
|
||||||
|
if (payoutDelayTimer == null) {
|
||||||
|
confirmButton.setText(Res.get("portfolio.pending.step3_seller.releaseBitcoin"));
|
||||||
|
confirmButton.setOnAction(e -> onReleaseBitcoin());
|
||||||
|
payoutDelayTimer = UserThread.runPeriodically(this::updateConfirmButtonDisableState, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (!model.dataModel.requiredPayoutDelayHasPassed()) {
|
||||||
|
new Popup().warning(Res.get("portfolio.pending.step3_seller.delayedPayout",
|
||||||
|
Objects.requireNonNull(model.dataModel.getTrade()).getShortId(),
|
||||||
|
DisplayUtils.formatDateTime(model.dataModel.getDelayedPayoutDate())))
|
||||||
|
.closeButtonText(Res.get("shared.iUnderstand"))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (payoutDelayTimer != null) {
|
||||||
|
payoutDelayTimer.stop();
|
||||||
|
payoutDelayTimer = null;
|
||||||
|
}
|
||||||
|
confirmButton.setText(Res.get("portfolio.pending.step3_seller.confirmReceipt"));
|
||||||
|
confirmButton.setOnAction(e -> onPaymentReceived());
|
||||||
|
}
|
||||||
|
updateConfirmButtonDisableState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfirmButtonDisableState() {
|
||||||
|
boolean confirmedReceiptAndRequiredDelayNotPassed = model.dataModel.getSellerConfirmedPaymentReceiptDate() > 0 &&
|
||||||
|
model.dataModel.requiresPayoutDelay() &&
|
||||||
|
!model.dataModel.requiredPayoutDelayHasPassed();
|
||||||
|
confirmButton.setDisable(!trade.confirmPermitted() || confirmedReceiptAndRequiredDelayNotPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onReleaseBitcoin() {
|
||||||
|
if (!model.dataModel.requiresPayoutDelay() || model.dataModel.requiredPayoutDelayHasPassed()) {
|
||||||
|
busyAnimation.play();
|
||||||
|
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||||
|
|
||||||
|
model.dataModel.onFiatPaymentReceived(() -> {
|
||||||
|
}, errorMessage -> {
|
||||||
|
busyAnimation.stop();
|
||||||
|
new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.error("Release Bitcoin is only permitted if no delayed payout is required or delay has passed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> getOptionalHolderName() {
|
private Optional<String> getOptionalHolderName() {
|
||||||
|
Loading…
Reference in New Issue
Block a user