mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-19 19:58:28 -04:00
support cash by atm payment method #626
This commit is contained in:
parent
92fb41fffa
commit
655583477a
22 changed files with 410 additions and 95 deletions
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.desktop.components.paymentmethods;
|
||||
|
||||
import com.jfoenix.controls.JFXTextArea;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.locale.CurrencyUtil;
|
||||
import haveno.core.locale.Res;
|
||||
import haveno.core.locale.TradeCurrency;
|
||||
import haveno.core.payment.CashByAtmAccount;
|
||||
import haveno.core.payment.PaymentAccount;
|
||||
import haveno.core.payment.payload.CashByAtmAccountPayload;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
import haveno.core.util.coin.CoinFormatter;
|
||||
import haveno.core.util.validation.InputValidator;
|
||||
import haveno.desktop.util.Layout;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import static haveno.desktop.util.FormBuilder.addCompactTopLabelTextArea;
|
||||
import static haveno.desktop.util.FormBuilder.addCompactTopLabelTextField;
|
||||
import static haveno.desktop.util.FormBuilder.addTopLabelTextArea;
|
||||
import static haveno.desktop.util.FormBuilder.addTopLabelTextFieldWithCopyIcon;
|
||||
|
||||
public class CashByAtmForm extends PaymentMethodForm {
|
||||
private final CashByAtmAccount cashByAtmAccount;
|
||||
|
||||
public static int addFormForBuyer(GridPane gridPane, int gridRow,
|
||||
PaymentAccountPayload paymentAccountPayload) {
|
||||
CashByAtmAccountPayload cbm = (CashByAtmAccountPayload) paymentAccountPayload;
|
||||
addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1,
|
||||
Res.get("payment.shared.extraInfo"),
|
||||
cbm.getExtraInfo(),
|
||||
Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
|
||||
TextArea textExtraInfo = addCompactTopLabelTextArea(gridPane, gridRow, 1, Res.get("payment.shared.extraInfo"), "").second;
|
||||
textExtraInfo.setMinHeight(70);
|
||||
textExtraInfo.setEditable(false);
|
||||
textExtraInfo.setText(cbm.getExtraInfo());
|
||||
return gridRow;
|
||||
}
|
||||
|
||||
public CashByAtmForm(PaymentAccount paymentAccount,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
InputValidator inputValidator, GridPane gridPane, int gridRow, CoinFormatter formatter) {
|
||||
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
|
||||
this.cashByAtmAccount = (CashByAtmAccount) paymentAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForAddAccount() {
|
||||
gridRowFrom = gridRow + 1;
|
||||
|
||||
addTradeCurrencyComboBox();
|
||||
currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedFiatCurrencies()));
|
||||
|
||||
TextArea extraTextArea = addTopLabelTextArea(gridPane, ++gridRow,
|
||||
Res.get("payment.shared.optionalExtra"), Res.get("payment.cashByAtm.extraInfo.prompt")).second;
|
||||
extraTextArea.setMinHeight(70);
|
||||
((JFXTextArea) extraTextArea).setLabelFloat(false);
|
||||
extraTextArea.textProperty().addListener((ov, oldValue, newValue) -> {
|
||||
cashByAtmAccount.setExtraInfo(newValue);
|
||||
updateFromInputs();
|
||||
});
|
||||
|
||||
addLimitations(false);
|
||||
addAccountNameTextFieldWithAutoFillToggleButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void autoFillNameTextField() {
|
||||
setAccountNameWithString(cashByAtmAccount.getExtraInfo().substring(0, Math.min(50, cashByAtmAccount.getExtraInfo().length())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormForEditAccount() {
|
||||
gridRowFrom = gridRow;
|
||||
addAccountNameTextFieldWithAutoFillToggleButton();
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"),
|
||||
Res.get(cashByAtmAccount.getPaymentMethod().getId()));
|
||||
|
||||
TradeCurrency tradeCurrency = paymentAccount.getSingleTradeCurrency();
|
||||
String nameAndCode = tradeCurrency != null ? tradeCurrency.getNameAndCode() : "";
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.currency"), nameAndCode);
|
||||
|
||||
TextArea textAreaExtra = addCompactTopLabelTextArea(gridPane, ++gridRow, Res.get("payment.shared.extraInfo"), "").second;
|
||||
textAreaExtra.setText(cashByAtmAccount.getExtraInfo());
|
||||
textAreaExtra.setMinHeight(70);
|
||||
textAreaExtra.setEditable(false);
|
||||
|
||||
addLimitations(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAllInputsValid() {
|
||||
allInputsValid.set(isAccountNameValid()
|
||||
&& !cashByAtmAccount.getExtraInfo().isEmpty()
|
||||
&& paymentAccount.getSingleTradeCurrency() != null);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import haveno.core.locale.Res;
|
|||
import haveno.core.offer.OfferRestrictions;
|
||||
import haveno.core.payment.AmazonGiftCardAccount;
|
||||
import haveno.core.payment.AustraliaPayidAccount;
|
||||
import haveno.core.payment.CashByAtmAccount;
|
||||
import haveno.core.payment.PayByMailAccount;
|
||||
import haveno.core.payment.CashDepositAccount;
|
||||
import haveno.core.payment.ZelleAccount;
|
||||
|
@ -72,6 +73,7 @@ import haveno.desktop.components.paymentmethods.AmazonGiftCardForm;
|
|||
import haveno.desktop.components.paymentmethods.AustraliaPayidForm;
|
||||
import haveno.desktop.components.paymentmethods.BizumForm;
|
||||
import haveno.desktop.components.paymentmethods.CapitualForm;
|
||||
import haveno.desktop.components.paymentmethods.CashByAtmForm;
|
||||
import haveno.desktop.components.paymentmethods.PayByMailForm;
|
||||
import haveno.desktop.components.paymentmethods.CashDepositForm;
|
||||
import haveno.desktop.components.paymentmethods.CelPayForm;
|
||||
|
@ -270,6 +272,14 @@ public class TraditionalAccountsView extends PaymentAccountsView<GridPane, Tradi
|
|||
.actionButtonText(Res.get("shared.iUnderstand"))
|
||||
.onAction(() -> doSaveNewAccount(paymentAccount))
|
||||
.show();
|
||||
} else if (paymentAccount instanceof CashByAtmAccount) {
|
||||
// CashByAtm has no chargeback risk so we don't show the text from payment.limits.info.
|
||||
new Popup().information(Res.get("payment.cashByAtm.info"))
|
||||
.width(850)
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.actionButtonText(Res.get("shared.iUnderstand"))
|
||||
.onAction(() -> doSaveNewAccount(paymentAccount))
|
||||
.show();
|
||||
} else if (paymentAccount instanceof HalCashAccount) {
|
||||
// HalCash has no chargeback risk so we don't show the text from payment.limits.info.
|
||||
new Popup().information(Res.get("payment.halCash.info"))
|
||||
|
@ -559,6 +569,8 @@ public class TraditionalAccountsView extends PaymentAccountsView<GridPane, Tradi
|
|||
return new CashDepositForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.PAY_BY_MAIL_ID:
|
||||
return new PayByMailForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.CASH_BY_ATM_ID:
|
||||
return new CashByAtmForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.HAL_CASH_ID:
|
||||
return new HalCashForm(paymentAccount, accountAgeWitnessService, halCashValidator, inputValidator, root, gridRow, formatter);
|
||||
case PaymentMethod.F2F_ID:
|
||||
|
|
|
@ -32,6 +32,7 @@ import haveno.core.offer.OfferDirection;
|
|||
import haveno.core.offer.OfferUtil;
|
||||
import haveno.core.offer.OpenOfferManager;
|
||||
import haveno.core.payment.PaymentAccount;
|
||||
import haveno.core.payment.payload.PaymentMethod;
|
||||
import haveno.core.provider.price.PriceFeedService;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.handlers.TransactionResultHandler;
|
||||
|
@ -81,7 +82,6 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
public abstract class MutableOfferDataModel extends OfferDataModel {
|
||||
|
@ -503,12 +503,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||
|
||||
private Volume calculateVolumeForAmount(ObjectProperty<BigInteger> minAmount) {
|
||||
Volume volumeByAmount = price.get().getVolumeByAmount(minAmount.get());
|
||||
|
||||
// For HalCash we want multiple of 10 EUR
|
||||
if (isUsingHalCashAccount())
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, paymentAccount.getPaymentMethod().getId());
|
||||
return volumeByAmount;
|
||||
}
|
||||
|
||||
|
@ -516,10 +511,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||
if (isNonZeroPrice.test(price) && isNonZeroVolume.test(volume) && allowAmountUpdate) {
|
||||
try {
|
||||
BigInteger value = HavenoUtils.coinToAtomicUnits(DisplayUtils.reduceTo4Decimals(HavenoUtils.atomicUnitsToCoin(price.get().getAmountByVolume(volume.get())), btcFormatter));
|
||||
if (isUsingHalCashAccount())
|
||||
value = CoinUtil.getAdjustedAmountForHalCash(value, price.get(), getMaxTradeLimit());
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
value = CoinUtil.getRoundedFiatAmount(value, price.get(), getMaxTradeLimit());
|
||||
value = CoinUtil.getRoundedAmount(value, price.get(), getMaxTradeLimit(), tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId());
|
||||
|
||||
calculateVolume();
|
||||
|
||||
|
@ -680,7 +672,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||
this.triggerPrice = triggerPrice;
|
||||
}
|
||||
|
||||
public boolean isUsingHalCashAccount() {
|
||||
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID);
|
||||
public boolean isUsingRoundedAtmCashAccount() {
|
||||
return PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
|
||||
import static haveno.desktop.main.offer.OfferViewUtil.addPayInfoEntry;
|
||||
import static haveno.desktop.util.FormBuilder.add2ButtonsAfterGroup;
|
||||
import static haveno.desktop.util.FormBuilder.addAddressTextField;
|
||||
|
@ -828,7 +827,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
|||
int marketPriceAvailableValue = model.marketPriceAvailableProperty.get();
|
||||
if (marketPriceAvailableValue > -1) {
|
||||
boolean showPriceToggle = marketPriceAvailableValue == 1 &&
|
||||
!model.getDataModel().paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID);
|
||||
!PaymentMethod.isFixedPriceOnly(model.getDataModel().paymentAccount.getPaymentMethod().getId());
|
||||
percentagePriceBox.setVisible(showPriceToggle);
|
||||
priceTypeToggleButton.setVisible(showPriceToggle);
|
||||
boolean fixedPriceSelected = !model.getDataModel().getUseMarketBasedPrice().get() || !showPriceToggle;
|
||||
|
|
|
@ -841,12 +841,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
|
||||
Volume volume = dataModel.getVolume().get();
|
||||
if (volume != null) {
|
||||
// For HalCash we want multiple of 10 EUR
|
||||
if (dataModel.isUsingHalCashAccount())
|
||||
volume = VolumeUtil.getAdjustedVolumeForHalCash(volume);
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
volume = VolumeUtil.getRoundedFiatVolume(volume);
|
||||
|
||||
volume = VolumeUtil.getAdjustedVolume(volume, dataModel.getPaymentAccount().getPaymentMethod().getId());
|
||||
this.volume.set(VolumeUtil.formatVolume(volume));
|
||||
}
|
||||
|
||||
|
@ -1082,10 +1077,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
Price price = dataModel.getPrice().get();
|
||||
if (price != null && price.isPositive()) {
|
||||
if (dataModel.isUsingHalCashAccount())
|
||||
amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit);
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
amount = CoinUtil.getRoundedFiatAmount(amount, price, maxTradeLimit);
|
||||
amount = CoinUtil.getRoundedAmount(amount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId());
|
||||
}
|
||||
dataModel.setAmount(amount);
|
||||
if (syncMinAmountWithAmount ||
|
||||
|
@ -1106,10 +1098,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||
Price price = dataModel.getPrice().get();
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
if (price != null && price.isPositive()) {
|
||||
if (dataModel.isUsingHalCashAccount())
|
||||
minAmount = CoinUtil.getAdjustedAmountForHalCash(minAmount, price, maxTradeLimit);
|
||||
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
|
||||
minAmount = CoinUtil.getRoundedFiatAmount(minAmount, price, maxTradeLimit);
|
||||
minAmount = CoinUtil.getRoundedAmount(minAmount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId());
|
||||
}
|
||||
|
||||
dataModel.setMinAmount(minAmount);
|
||||
|
|
|
@ -63,7 +63,6 @@ import java.util.Set;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
|
||||
|
||||
/**
|
||||
* Domain for that UI element.
|
||||
|
@ -376,10 +375,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
amount.get() != null &&
|
||||
amount.get().compareTo(BigInteger.valueOf(0)) != 0) {
|
||||
Volume volumeByAmount = tradePrice.getVolumeByAmount(amount.get());
|
||||
if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID))
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
|
||||
else if (offer.isFiatOffer())
|
||||
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
|
||||
volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, offer.getPaymentMethod().getId());
|
||||
|
||||
volume.set(volumeByAmount);
|
||||
|
||||
|
@ -491,7 +487,7 @@ class TakeOfferDataModel extends OfferDataModel {
|
|||
return offer.getSellerSecurityDeposit();
|
||||
}
|
||||
|
||||
public boolean isUsingHalCashAccount() {
|
||||
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID);
|
||||
public boolean isRoundedForAtmCash() {
|
||||
return PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -302,18 +302,19 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
|
||||
Price tradePrice = dataModel.tradePrice;
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
if (dataModel.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID)) {
|
||||
BigInteger adjustedAmountForHalCash = CoinUtil.getAdjustedAmountForHalCash(dataModel.getAmount().get(),
|
||||
if (PaymentMethod.isRoundedForAtmCash(dataModel.getPaymentMethod().getId())) {
|
||||
BigInteger adjustedAmountForHalCash = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(),
|
||||
tradePrice,
|
||||
maxTradeLimit);
|
||||
dataModel.applyAmount(adjustedAmountForHalCash);
|
||||
amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get()));
|
||||
} else if (dataModel.getOffer().isFiatOffer()) {
|
||||
} else if (dataModel.getOffer().isTraditionalOffer()) {
|
||||
if (!isAmountEqualMinAmount(dataModel.getAmount().get()) && (!isAmountEqualMaxAmount(dataModel.getAmount().get()))) {
|
||||
// We only apply the rounding if the amount is variable (minAmount is lower as amount).
|
||||
// Otherwise we could get an amount lower then the minAmount set by rounding
|
||||
BigInteger roundedAmount = CoinUtil.getRoundedFiatAmount(dataModel.getAmount().get(), tradePrice,
|
||||
maxTradeLimit);
|
||||
BigInteger roundedAmount = dataModel.getOffer().isFiatOffer() ?
|
||||
CoinUtil.getRoundedFiatAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit) :
|
||||
CoinUtil.getRoundedTraditionalAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit);
|
||||
dataModel.applyAmount(roundedAmount);
|
||||
}
|
||||
amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get()));
|
||||
|
@ -585,13 +586,15 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
Price price = dataModel.tradePrice;
|
||||
if (price != null) {
|
||||
if (dataModel.isUsingHalCashAccount()) {
|
||||
amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit);
|
||||
} else if (dataModel.getOffer().isFiatOffer()
|
||||
if (dataModel.isRoundedForAtmCash()) {
|
||||
amount = CoinUtil.getRoundedAtmCashAmount(amount, price, maxTradeLimit);
|
||||
} else if (dataModel.getOffer().isTraditionalOffer()
|
||||
&& !isAmountEqualMinAmount(amount) && !isAmountEqualMaxAmount(amount)) {
|
||||
// We only apply the rounding if the amount is variable (minAmount is lower as amount).
|
||||
// Otherwise we could get an amount lower then the minAmount set by rounding
|
||||
amount = CoinUtil.getRoundedFiatAmount(amount, price, maxTradeLimit);
|
||||
amount = dataModel.getOffer().isFiatOffer() ?
|
||||
CoinUtil.getRoundedFiatAmount(amount, price, maxTradeLimit) :
|
||||
CoinUtil.getRoundedTraditionalAmount(amount, price, maxTradeLimit);
|
||||
}
|
||||
}
|
||||
dataModel.applyAmount(amount);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue