support cash by atm payment method #626

This commit is contained in:
woodser 2023-05-31 08:21:14 -04:00
parent 92fb41fffa
commit 655583477a
22 changed files with 410 additions and 95 deletions

View File

@ -81,6 +81,12 @@ public class CurrencyUtil {
public static List<TradeCurrency> getAllFiatCurrencies() { public static List<TradeCurrency> getAllFiatCurrencies() {
return getAllTraditionalCurrencies().stream() return getAllTraditionalCurrencies().stream()
.filter(currency -> CurrencyUtil.isFiatCurrency(currency.getCode()))
.collect(Collectors.toList());
}
public static List<TradeCurrency> getAllSortedFiatCurrencies() {
return getAllSortedTraditionalCurrencies().stream()
.filter(currency -> CurrencyUtil.isFiatCurrency(currency.getCode())) .filter(currency -> CurrencyUtil.isFiatCurrency(currency.getCode()))
.collect(Collectors.toList()); // sorted by currency name .collect(Collectors.toList()); // sorted by currency name
} }

View File

@ -25,6 +25,7 @@ import haveno.core.locale.Res;
import haveno.core.monetary.Price; import haveno.core.monetary.Price;
import haveno.core.payment.PaymentAccount; import haveno.core.payment.PaymentAccount;
import haveno.core.payment.PaymentAccountUtil; import haveno.core.payment.PaymentAccountUtil;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.provider.price.MarketPrice; import haveno.core.provider.price.MarketPrice;
import haveno.core.provider.price.PriceFeedService; import haveno.core.provider.price.PriceFeedService;
import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
@ -47,8 +48,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
@Slf4j @Slf4j
@Singleton @Singleton
public class CreateOfferService { public class CreateOfferService {
@ -137,7 +136,7 @@ public class CreateOfferService {
boolean useMarketBasedPriceValue = price == null && boolean useMarketBasedPriceValue = price == null &&
useMarketBasedPrice && useMarketBasedPrice &&
isMarketPriceAvailable(currencyCode) && isMarketPriceAvailable(currencyCode) &&
!paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); !PaymentMethod.isFixedPriceOnly(paymentAccount.getPaymentMethod().getId());
// verify price // verify price
if (price == null && !useMarketBasedPriceValue) { if (price == null && !useMarketBasedPriceValue) {

View File

@ -246,10 +246,7 @@ public class Offer implements NetworkPayload, PersistablePayload {
return null; return null;
} }
Volume volumeByAmount = price.getVolumeByAmount(amount); Volume volumeByAmount = price.getVolumeByAmount(amount);
if (offerPayload.getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID)) volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, getPaymentMethod().getId());
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (isFiatOffer())
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
return volumeByAmount; return volumeByAmount;
} }

View File

@ -19,15 +19,14 @@ package haveno.core.offer.takeoffer;
import haveno.common.taskrunner.Model; import haveno.common.taskrunner.Model;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.Price; import haveno.core.monetary.Price;
import haveno.core.monetary.Volume; import haveno.core.monetary.Volume;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.offer.OfferUtil; import haveno.core.offer.OfferUtil;
import haveno.core.payment.PaymentAccount; import haveno.core.payment.PaymentAccount;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.provider.price.PriceFeedService; import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.util.VolumeUtil;
import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import lombok.Getter; import lombok.Getter;
@ -41,8 +40,6 @@ import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static haveno.core.offer.OfferDirection.SELL; import static haveno.core.offer.OfferDirection.SELL;
import static haveno.core.util.VolumeUtil.getAdjustedVolumeForHalCash;
import static haveno.core.util.VolumeUtil.getRoundedFiatVolume;
import static haveno.core.xmr.model.XmrAddressEntry.Context.OFFER_FUNDING; import static haveno.core.xmr.model.XmrAddressEntry.Context.OFFER_FUNDING;
@Slf4j @Slf4j
@ -136,11 +133,7 @@ public class TakeOfferModel implements Model {
private void calculateVolume() { private void calculateVolume() {
Price tradePrice = offer.getPrice(); Price tradePrice = offer.getPrice();
Volume volumeByAmount = Objects.requireNonNull(tradePrice).getVolumeByAmount(amount); Volume volumeByAmount = Objects.requireNonNull(tradePrice).getVolumeByAmount(amount);
volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, offer.getPaymentMethod().getId());
if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID))
volumeByAmount = getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()))
volumeByAmount = getRoundedFiatVolume(volumeByAmount);
volume = volumeByAmount; volume = volumeByAmount;

View File

@ -0,0 +1,64 @@
/*
* 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.core.payment;
import haveno.core.api.model.PaymentAccountFormField;
import haveno.core.locale.CurrencyUtil;
import haveno.core.locale.TradeCurrency;
import haveno.core.payment.payload.CashByAtmAccountPayload;
import haveno.core.payment.payload.PaymentAccountPayload;
import haveno.core.payment.payload.PaymentMethod;
import lombok.NonNull;
import java.util.List;
public final class CashByAtmAccount extends PaymentAccount {
public static final List<TradeCurrency> SUPPORTED_CURRENCIES = CurrencyUtil.getAllFiatCurrencies();
private static final List<PaymentAccountFormField.FieldId> INPUT_FIELD_IDS = List.of(
PaymentAccountFormField.FieldId.EXTRA_INFO
);
public CashByAtmAccount() {
super(PaymentMethod.CASH_BY_ATM);
}
@Override
protected PaymentAccountPayload createPayload() {
return new CashByAtmAccountPayload(paymentMethod.getId(), id);
}
@Override
public @NonNull List<TradeCurrency> getSupportedCurrencies() {
return SUPPORTED_CURRENCIES;
}
@Override
public @NonNull List<PaymentAccountFormField.FieldId> getInputFieldIds() {
return INPUT_FIELD_IDS;
}
public void setExtraInfo(String extraInfo) {
((CashByAtmAccountPayload) paymentAccountPayload).setExtraInfo(extraInfo);
}
public String getExtraInfo() {
return ((CashByAtmAccountPayload) paymentAccountPayload).getExtraInfo();
}
}

View File

@ -76,6 +76,8 @@ public class PaymentAccountFactory {
return new F2FAccount(); return new F2FAccount();
case PaymentMethod.PAY_BY_MAIL_ID: case PaymentMethod.PAY_BY_MAIL_ID:
return new PayByMailAccount(); return new PayByMailAccount();
case PaymentMethod.CASH_BY_ATM_ID:
return new CashByAtmAccount();
case PaymentMethod.PROMPT_PAY_ID: case PaymentMethod.PROMPT_PAY_ID:
return new PromptPayAccount(); return new PromptPayAccount();
case PaymentMethod.ADVANCED_CASH_ID: case PaymentMethod.ADVANCED_CASH_ID:

View File

@ -0,0 +1,96 @@
/*
* 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.core.payment.payload;
import com.google.protobuf.Message;
import haveno.core.locale.Res;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@EqualsAndHashCode(callSuper = true)
@ToString
@Setter
@Getter
@Slf4j
public final class CashByAtmAccountPayload extends PaymentAccountPayload {
private String extraInfo = "";
public CashByAtmAccountPayload(String paymentMethod, String id) {
super(paymentMethod, id);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private CashByAtmAccountPayload(String paymentMethod, String id,
String extraInfo,
long maxTradePeriod,
Map<String, String> excludeFromJsonDataMap) {
super(paymentMethod,
id,
maxTradePeriod,
excludeFromJsonDataMap);
this.extraInfo = extraInfo;
}
@Override
public Message toProtoMessage() {
return getPaymentAccountPayloadBuilder()
.setCashByAtmAccountPayload(protobuf.CashByAtmAccountPayload.newBuilder()
.setExtraInfo(extraInfo))
.build();
}
public static CashByAtmAccountPayload fromProto(protobuf.PaymentAccountPayload proto) {
return new CashByAtmAccountPayload(proto.getPaymentMethodId(),
proto.getId(),
proto.getCashByAtmAccountPayload().getExtraInfo(),
proto.getMaxTradePeriod(),
new HashMap<>(proto.getExcludeFromJsonDataMap()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public String getPaymentDetails() {
return Res.getWithCol("payment.shared.extraInfo") + " " + extraInfo;
}
@Override
public String getPaymentDetailsForTradePopup() {
return Res.getWithCol("payment.shared.extraInfo") + " " + extraInfo;
}
@Override
public byte[] getAgeWitnessInputData() {
return super.getAgeWitnessInputData(ArrayUtils.addAll(id.getBytes(StandardCharsets.UTF_8)));
}
}

View File

@ -31,6 +31,7 @@ import haveno.core.payment.AmazonGiftCardAccount;
import haveno.core.payment.AustraliaPayidAccount; import haveno.core.payment.AustraliaPayidAccount;
import haveno.core.payment.BizumAccount; import haveno.core.payment.BizumAccount;
import haveno.core.payment.CapitualAccount; import haveno.core.payment.CapitualAccount;
import haveno.core.payment.CashByAtmAccount;
import haveno.core.payment.PayByMailAccount; import haveno.core.payment.PayByMailAccount;
import haveno.core.payment.CashDepositAccount; import haveno.core.payment.CashDepositAccount;
import haveno.core.payment.CelPayAccount; import haveno.core.payment.CelPayAccount;
@ -162,6 +163,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
public static final String AMAZON_GIFT_CARD_ID = "AMAZON_GIFT_CARD"; public static final String AMAZON_GIFT_CARD_ID = "AMAZON_GIFT_CARD";
public static final String BLOCK_CHAINS_INSTANT_ID = "BLOCK_CHAINS_INSTANT"; public static final String BLOCK_CHAINS_INSTANT_ID = "BLOCK_CHAINS_INSTANT";
public static final String PAY_BY_MAIL_ID = "PAY_BY_MAIL"; public static final String PAY_BY_MAIL_ID = "PAY_BY_MAIL";
public static final String CASH_BY_ATM_ID = "CASH_BY_ATM";
public static final String CAPITUAL_ID = "CAPITUAL"; public static final String CAPITUAL_ID = "CAPITUAL";
public static final String CELPAY_ID = "CELPAY"; public static final String CELPAY_ID = "CELPAY";
public static final String MONESE_ID = "MONESE"; public static final String MONESE_ID = "MONESE";
@ -224,6 +226,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
public static PaymentMethod AMAZON_GIFT_CARD; public static PaymentMethod AMAZON_GIFT_CARD;
public static PaymentMethod BLOCK_CHAINS_INSTANT; public static PaymentMethod BLOCK_CHAINS_INSTANT;
public static PaymentMethod PAY_BY_MAIL; public static PaymentMethod PAY_BY_MAIL;
public static PaymentMethod CASH_BY_ATM;
public static PaymentMethod CAPITUAL; public static PaymentMethod CAPITUAL;
public static PaymentMethod CELPAY; public static PaymentMethod CELPAY;
public static PaymentMethod MONESE; public static PaymentMethod MONESE;
@ -271,6 +274,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
// Global // Global
CASH_DEPOSIT = new PaymentMethod(CASH_DEPOSIT_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(CashDepositAccount.SUPPORTED_CURRENCIES)), CASH_DEPOSIT = new PaymentMethod(CASH_DEPOSIT_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(CashDepositAccount.SUPPORTED_CURRENCIES)),
PAY_BY_MAIL = new PaymentMethod(PAY_BY_MAIL_ID, 8 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(PayByMailAccount.SUPPORTED_CURRENCIES)), PAY_BY_MAIL = new PaymentMethod(PAY_BY_MAIL_ID, 8 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(PayByMailAccount.SUPPORTED_CURRENCIES)),
CASH_BY_ATM = new PaymentMethod(CASH_BY_ATM_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(CashByAtmAccount.SUPPORTED_CURRENCIES)),
MONEY_GRAM = new PaymentMethod(MONEY_GRAM_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_MID_RISK, getAssetCodes(MoneyGramAccount.SUPPORTED_CURRENCIES)), MONEY_GRAM = new PaymentMethod(MONEY_GRAM_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_MID_RISK, getAssetCodes(MoneyGramAccount.SUPPORTED_CURRENCIES)),
WESTERN_UNION = new PaymentMethod(WESTERN_UNION_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_MID_RISK, getAssetCodes(WesternUnionAccount.SUPPORTED_CURRENCIES)), WESTERN_UNION = new PaymentMethod(WESTERN_UNION_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_MID_RISK, getAssetCodes(WesternUnionAccount.SUPPORTED_CURRENCIES)),
NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(NationalBankAccount.SUPPORTED_CURRENCIES)), NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 4 * DAY, DEFAULT_TRADE_LIMIT_HIGH_RISK, getAssetCodes(NationalBankAccount.SUPPORTED_CURRENCIES)),
@ -560,4 +564,14 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
id.equals(PaymentMethod.MONEY_BEAM_ID) || id.equals(PaymentMethod.MONEY_BEAM_ID) ||
id.equals(PaymentMethod.UPHOLD_ID); id.equals(PaymentMethod.UPHOLD_ID);
} }
public static boolean isRoundedForAtmCash(String id) {
return id.equals(PaymentMethod.CASH_BY_ATM_ID) ||
id.equals(PaymentMethod.HAL_CASH_ID);
}
public static boolean isFixedPriceOnly(String id) {
return id.equals(PaymentMethod.CASH_BY_ATM_ID) ||
id.equals(PaymentMethod.HAL_CASH_ID);
}
} }

View File

@ -30,6 +30,7 @@ import haveno.core.payment.payload.AustraliaPayidAccountPayload;
import haveno.core.payment.payload.BizumAccountPayload; import haveno.core.payment.payload.BizumAccountPayload;
import haveno.core.payment.payload.CapitualAccountPayload; import haveno.core.payment.payload.CapitualAccountPayload;
import haveno.core.payment.payload.CashAppAccountPayload; import haveno.core.payment.payload.CashAppAccountPayload;
import haveno.core.payment.payload.CashByAtmAccountPayload;
import haveno.core.payment.payload.PayByMailAccountPayload; import haveno.core.payment.payload.PayByMailAccountPayload;
import haveno.core.payment.payload.CashDepositAccountPayload; import haveno.core.payment.payload.CashDepositAccountPayload;
import haveno.core.payment.payload.CelPayAccountPayload; import haveno.core.payment.payload.CelPayAccountPayload;
@ -201,6 +202,8 @@ public class CoreProtoResolver implements ProtoResolver {
return USPostalMoneyOrderAccountPayload.fromProto(proto); return USPostalMoneyOrderAccountPayload.fromProto(proto);
case PAY_BY_MAIL_ACCOUNT_PAYLOAD: case PAY_BY_MAIL_ACCOUNT_PAYLOAD:
return PayByMailAccountPayload.fromProto(proto); return PayByMailAccountPayload.fromProto(proto);
case CASH_BY_ATM_ACCOUNT_PAYLOAD:
return CashByAtmAccountPayload.fromProto(proto);
case PROMPT_PAY_ACCOUNT_PAYLOAD: case PROMPT_PAY_ACCOUNT_PAYLOAD:
return PromptPayAccountPayload.fromProto(proto); return PromptPayAccountPayload.fromProto(proto);
case ADVANCED_CASH_ACCOUNT_PAYLOAD: case ADVANCED_CASH_ACCOUNT_PAYLOAD:

View File

@ -22,7 +22,6 @@ import haveno.common.crypto.PubKeyRing;
import haveno.common.proto.network.NetworkPayload; import haveno.common.proto.network.NetworkPayload;
import haveno.common.util.JsonExclude; import haveno.common.util.JsonExclude;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.Price; import haveno.core.monetary.Price;
import haveno.core.monetary.Volume; import haveno.core.monetary.Volume;
import haveno.core.offer.OfferPayload; import haveno.core.offer.OfferPayload;
@ -204,12 +203,7 @@ public final class Contract implements NetworkPayload {
public Volume getTradeVolume() { public Volume getTradeVolume() {
Volume volumeByAmount = getPrice().getVolumeByAmount(getTradeAmount()); Volume volumeByAmount = getPrice().getVolumeByAmount(getTradeAmount());
volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, getPaymentMethodId());
if (getPaymentMethodId().equals(PaymentMethod.HAL_CASH_ID))
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(getOfferPayload().getCurrencyCode()))
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
return volumeByAmount; return volumeByAmount;
} }

View File

@ -28,13 +28,11 @@ import haveno.common.proto.ProtoUtil;
import haveno.common.taskrunner.Model; import haveno.common.taskrunner.Model;
import haveno.common.util.Utilities; import haveno.common.util.Utilities;
import haveno.core.api.CoreMoneroConnectionsService; import haveno.core.api.CoreMoneroConnectionsService;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.Price; import haveno.core.monetary.Price;
import haveno.core.monetary.Volume; import haveno.core.monetary.Volume;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.offer.OfferDirection; import haveno.core.offer.OfferDirection;
import haveno.core.payment.payload.PaymentAccountPayload; import haveno.core.payment.payload.PaymentAccountPayload;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.proto.CoreProtoResolver; import haveno.core.proto.CoreProtoResolver;
import haveno.core.proto.network.CoreNetworkProtoResolver; import haveno.core.proto.network.CoreNetworkProtoResolver;
import haveno.core.support.dispute.Dispute; import haveno.core.support.dispute.Dispute;
@ -1438,14 +1436,7 @@ public abstract class Trade implements Tradable, Model {
try { try {
if (getAmount() != null && getPrice() != null) { if (getAmount() != null && getPrice() != null) {
Volume volumeByAmount = getPrice().getVolumeByAmount(getAmount()); Volume volumeByAmount = getPrice().getVolumeByAmount(getAmount());
if (offer != null) { if (offer != null) volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, offer.getPaymentMethod().getId());
if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID))
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()))
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
else if (CurrencyUtil.isTraditionalCurrency(offer.getCurrencyCode()))
volumeByAmount = VolumeUtil.getRoundedTraditionalVolume(volumeByAmount);
}
return volumeByAmount; return volumeByAmount;
} else { } else {
return null; return null;

View File

@ -26,6 +26,7 @@ import haveno.core.monetary.TraditionalMoney;
import haveno.core.monetary.TraditionalExchangeRate; import haveno.core.monetary.TraditionalExchangeRate;
import haveno.core.monetary.Volume; import haveno.core.monetary.Volume;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import org.bitcoinj.core.Monetary; import org.bitcoinj.core.Monetary;
import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.MonetaryFormat;
@ -42,23 +43,33 @@ public class VolumeUtil {
private static double EXPONENT = Math.pow(10, TraditionalMoney.SMALLEST_UNIT_EXPONENT); // 1000000000000 with precision 8 private static double EXPONENT = Math.pow(10, TraditionalMoney.SMALLEST_UNIT_EXPONENT); // 1000000000000 with precision 8
public static Volume getAdjustedVolume(Volume volumeByAmount, String paymentMethodId) {
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId))
return VolumeUtil.getRoundedAtmCashVolume(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(volumeByAmount.getCurrencyCode()))
return VolumeUtil.getRoundedFiatVolume(volumeByAmount);
else if (CurrencyUtil.isTraditionalCurrency(volumeByAmount.getCurrencyCode()))
return VolumeUtil.getRoundedTraditionalVolume(volumeByAmount);
return volumeByAmount;
}
public static Volume getRoundedFiatVolume(Volume volumeByAmount) { public static Volume getRoundedFiatVolume(Volume volumeByAmount) {
// We want to get rounded to 1 unit of the fiat currency, e.g. 1 EUR. // We want to get rounded to 1 unit of the fiat currency, e.g. 1 EUR.
return getAdjustedFiatVolume(volumeByAmount, 1); return getAdjustedFiatVolume(volumeByAmount, 1);
} }
private static Volume getRoundedAtmCashVolume(Volume volumeByAmount) {
// EUR has precision TraditionalMoney.SMALLEST_UNIT_EXPONENT and we want multiple of 10 so we divide by EXPONENT then
// round and multiply with 10
return getAdjustedFiatVolume(volumeByAmount, 10);
}
public static Volume getRoundedTraditionalVolume(Volume volumeByAmount) { public static Volume getRoundedTraditionalVolume(Volume volumeByAmount) {
DecimalFormat decimalFormat = new DecimalFormat("#.####"); DecimalFormat decimalFormat = new DecimalFormat("#.####");
double roundedVolume = Double.parseDouble(decimalFormat.format(Double.parseDouble(volumeByAmount.toString()))); double roundedVolume = Double.parseDouble(decimalFormat.format(Double.parseDouble(volumeByAmount.toString())));
return Volume.parse(String.valueOf(roundedVolume), volumeByAmount.getCurrencyCode()); return Volume.parse(String.valueOf(roundedVolume), volumeByAmount.getCurrencyCode());
} }
public static Volume getAdjustedVolumeForHalCash(Volume volumeByAmount) {
// EUR has precision TraditionalMoney.SMALLEST_UNIT_EXPONENT and we want multiple of 10 so we divide by EXPONENT then
// round and multiply with 10
return getAdjustedFiatVolume(volumeByAmount, 10);
}
/** /**
* *
* @param volumeByAmount The volume generated from an amount * @param volumeByAmount The volume generated from an amount

View File

@ -19,14 +19,17 @@ package haveno.core.util.coin;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import haveno.common.util.MathUtils; import haveno.common.util.MathUtils;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.Price; import haveno.core.monetary.Price;
import haveno.core.monetary.Volume; import haveno.core.monetary.Volume;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.xmr.wallet.Restrictions; import haveno.core.xmr.wallet.Restrictions;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.text.DecimalFormat;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static haveno.core.util.VolumeUtil.getAdjustedFiatVolume; import static haveno.core.util.VolumeUtil.getAdjustedFiatVolume;
@ -76,6 +79,21 @@ public class CoinUtil {
return BigDecimal.valueOf(percent).multiply(new BigDecimal(amount)).toBigInteger(); return BigDecimal.valueOf(percent).multiply(new BigDecimal(amount)).toBigInteger();
} }
public static BigInteger getRoundedAmount(BigInteger amount, Price price, long maxTradeLimit, String currencyCode, String paymentMethodId) {
if (PaymentMethod.isRoundedForAtmCash(paymentMethodId)) {
return getRoundedAtmCashAmount(amount, price, maxTradeLimit);
} else if (CurrencyUtil.isFiatCurrency(currencyCode)) {
return getRoundedFiatAmount(amount, price, maxTradeLimit);
} else if (CurrencyUtil.isTraditionalCurrency(currencyCode)) {
return getRoundedTraditionalAmount(amount, price, maxTradeLimit);
}
return amount;
}
public static BigInteger getRoundedAtmCashAmount(BigInteger amount, Price price, long maxTradeLimit) {
return getAdjustedAmount(amount, price, maxTradeLimit, 10);
}
/** /**
* Calculate the possibly adjusted amount for {@code amount}, taking into account the * Calculate the possibly adjusted amount for {@code amount}, taking into account the
* {@code price} and {@code maxTradeLimit} and {@code factor}. * {@code price} and {@code maxTradeLimit} and {@code factor}.
@ -89,8 +107,10 @@ public class CoinUtil {
return getAdjustedAmount(amount, price, maxTradeLimit, 1); return getAdjustedAmount(amount, price, maxTradeLimit, 1);
} }
public static BigInteger getAdjustedAmountForHalCash(BigInteger amount, Price price, long maxTradeLimit) { public static BigInteger getRoundedTraditionalAmount(BigInteger amount, Price price, long maxTradeLimit) {
return getAdjustedAmount(amount, price, maxTradeLimit, 10); DecimalFormat decimalFormat = new DecimalFormat("#.####");
double roundedXmrAmount = Double.parseDouble(decimalFormat.format(HavenoUtils.atomicUnitsToXmr(amount)));
return HavenoUtils.xmrToAtomicUnits(roundedXmrAmount);
} }
/** /**

View File

@ -2943,13 +2943,6 @@ payment.payByMail.info=Trading using pay-by-mail (PBM) on Haveno requires that y
payment.payByMail.contact=Contact info payment.payByMail.contact=Contact info
payment.payByMail.contact.prompt=Name or nym envelope should be addressed to payment.payByMail.contact.prompt=Name or nym envelope should be addressed to
payment.f2f.contact=Contact info
payment.f2f.contact.prompt=How would you like to be contacted by the trading peer? (email address, phone number,...)
payment.f2f.city=City for 'Face to face' meeting
payment.f2f.city.prompt=The city will be displayed with the offer
payment.shared.optionalExtra=Optional additional information
payment.shared.extraInfo=Additional information
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.payByMail.extraInfo.prompt=Please state on your offers: \n\n\ payment.payByMail.extraInfo.prompt=Please state on your offers: \n\n\
Country you are located (eg France); \n\ Country you are located (eg France); \n\
Countries / regions you would accept trades from (eg France, EU, or any European country); \n\ Countries / regions you would accept trades from (eg France, EU, or any European country); \n\
@ -2957,6 +2950,23 @@ Any special terms/conditions; \n\
Any other details. Any other details.
payment.payByMail.tradingRestrictions=Please review the maker's terms and conditions.\n\ payment.payByMail.tradingRestrictions=Please review the maker's terms and conditions.\n\
If you do not meet the requirements do not take this trade. If you do not meet the requirements do not take this trade.
payment.cashByAtm.info=Cash at ATM: Cardless withdraw at ATM using code\n\n\
1. List your accepted banks, regions, or other terms.\n\n\
2. Chat with your peer trader to coordinate a time and share the withdraw code.\n\n\
If you cannot complete the transaction as specified in your trade contract, you may lose some (or all) of your security deposit.
payment.cashByAtm.extraInfo.prompt=Please state on your offers: \n\n\
Your accepted banks / locations; \n\
Any special terms/conditions; \n\
Any other details.
payment.payByMail.tradingRestrictions=Please review the maker's terms and conditions.\n\
If you do not meet the requirements do not take this trade.
payment.f2f.contact=Contact info
payment.f2f.contact.prompt=How would you like to be contacted by the trading peer? (email address, phone number,...)
payment.f2f.city=City for 'Face to face' meeting
payment.f2f.city.prompt=The city will be displayed with the offer
payment.shared.optionalExtra=Optional additional information
payment.shared.extraInfo=Additional information
payment.shared.extraInfo.prompt=Define any special terms, conditions, or details you would like to be displayed with your offers for this payment account (users will see this info before accepting offers).
payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\n\ payment.f2f.info='Face to Face' trades have different rules and come with different risks than online transactions.\n\n\
The main differences are:\n\ The main differences are:\n\
The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n\ The trading peers need to exchange information about the meeting location and time by using their provided contact details.\n\
@ -3001,6 +3011,7 @@ SPECIFIC_BANKS=Transfers with specific banks
US_POSTAL_MONEY_ORDER=US Postal Money Order US_POSTAL_MONEY_ORDER=US Postal Money Order
CASH_DEPOSIT=Cash Deposit CASH_DEPOSIT=Cash Deposit
PAY_BY_MAIL=Pay By Mail PAY_BY_MAIL=Pay By Mail
CASH_BY_ATM=Cash by ATM
MONEY_GRAM=MoneyGram MONEY_GRAM=MoneyGram
WESTERN_UNION=Western Union WESTERN_UNION=Western Union
F2F=Face to face (in person) F2F=Face to face (in person)
@ -3018,7 +3029,9 @@ US_POSTAL_MONEY_ORDER_SHORT=US Money Order
# suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty"
CASH_DEPOSIT_SHORT=Cash Deposit CASH_DEPOSIT_SHORT=Cash Deposit
# suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty"
PAY_BY_MAIL_SHORT=PayByMail PAY_BY_MAIL_SHORT=Pay By Mail
# suppress inspection "UnusedProperty"
CASH_BY_ATM_SHORT=Cash By ATM
# suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty"
MONEY_GRAM_SHORT=MoneyGram MONEY_GRAM_SHORT=MoneyGram
# suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty"

View File

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

View File

@ -26,6 +26,7 @@ import haveno.core.locale.Res;
import haveno.core.offer.OfferRestrictions; import haveno.core.offer.OfferRestrictions;
import haveno.core.payment.AmazonGiftCardAccount; import haveno.core.payment.AmazonGiftCardAccount;
import haveno.core.payment.AustraliaPayidAccount; import haveno.core.payment.AustraliaPayidAccount;
import haveno.core.payment.CashByAtmAccount;
import haveno.core.payment.PayByMailAccount; import haveno.core.payment.PayByMailAccount;
import haveno.core.payment.CashDepositAccount; import haveno.core.payment.CashDepositAccount;
import haveno.core.payment.ZelleAccount; 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.AustraliaPayidForm;
import haveno.desktop.components.paymentmethods.BizumForm; import haveno.desktop.components.paymentmethods.BizumForm;
import haveno.desktop.components.paymentmethods.CapitualForm; import haveno.desktop.components.paymentmethods.CapitualForm;
import haveno.desktop.components.paymentmethods.CashByAtmForm;
import haveno.desktop.components.paymentmethods.PayByMailForm; import haveno.desktop.components.paymentmethods.PayByMailForm;
import haveno.desktop.components.paymentmethods.CashDepositForm; import haveno.desktop.components.paymentmethods.CashDepositForm;
import haveno.desktop.components.paymentmethods.CelPayForm; import haveno.desktop.components.paymentmethods.CelPayForm;
@ -270,6 +272,14 @@ public class TraditionalAccountsView extends PaymentAccountsView<GridPane, Tradi
.actionButtonText(Res.get("shared.iUnderstand")) .actionButtonText(Res.get("shared.iUnderstand"))
.onAction(() -> doSaveNewAccount(paymentAccount)) .onAction(() -> doSaveNewAccount(paymentAccount))
.show(); .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) { } else if (paymentAccount instanceof HalCashAccount) {
// HalCash has no chargeback risk so we don't show the text from payment.limits.info. // HalCash has no chargeback risk so we don't show the text from payment.limits.info.
new Popup().information(Res.get("payment.halCash.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); return new CashDepositForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter);
case PaymentMethod.PAY_BY_MAIL_ID: case PaymentMethod.PAY_BY_MAIL_ID:
return new PayByMailForm(paymentAccount, accountAgeWitnessService, inputValidator, root, gridRow, formatter); 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: case PaymentMethod.HAL_CASH_ID:
return new HalCashForm(paymentAccount, accountAgeWitnessService, halCashValidator, inputValidator, root, gridRow, formatter); return new HalCashForm(paymentAccount, accountAgeWitnessService, halCashValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.F2F_ID: case PaymentMethod.F2F_ID:

View File

@ -32,6 +32,7 @@ import haveno.core.offer.OfferDirection;
import haveno.core.offer.OfferUtil; import haveno.core.offer.OfferUtil;
import haveno.core.offer.OpenOfferManager; import haveno.core.offer.OpenOfferManager;
import haveno.core.payment.PaymentAccount; import haveno.core.payment.PaymentAccount;
import haveno.core.payment.payload.PaymentMethod;
import haveno.core.provider.price.PriceFeedService; import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.trade.handlers.TransactionResultHandler; import haveno.core.trade.handlers.TransactionResultHandler;
@ -81,7 +82,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
public abstract class MutableOfferDataModel extends OfferDataModel { public abstract class MutableOfferDataModel extends OfferDataModel {
@ -503,12 +503,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
private Volume calculateVolumeForAmount(ObjectProperty<BigInteger> minAmount) { private Volume calculateVolumeForAmount(ObjectProperty<BigInteger> minAmount) {
Volume volumeByAmount = price.get().getVolumeByAmount(minAmount.get()); Volume volumeByAmount = price.get().getVolumeByAmount(minAmount.get());
volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, paymentAccount.getPaymentMethod().getId());
// For HalCash we want multiple of 10 EUR
if (isUsingHalCashAccount())
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
return volumeByAmount; return volumeByAmount;
} }
@ -516,10 +511,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
if (isNonZeroPrice.test(price) && isNonZeroVolume.test(volume) && allowAmountUpdate) { if (isNonZeroPrice.test(price) && isNonZeroVolume.test(volume) && allowAmountUpdate) {
try { try {
BigInteger value = HavenoUtils.coinToAtomicUnits(DisplayUtils.reduceTo4Decimals(HavenoUtils.atomicUnitsToCoin(price.get().getAmountByVolume(volume.get())), btcFormatter)); BigInteger value = HavenoUtils.coinToAtomicUnits(DisplayUtils.reduceTo4Decimals(HavenoUtils.atomicUnitsToCoin(price.get().getAmountByVolume(volume.get())), btcFormatter));
if (isUsingHalCashAccount()) value = CoinUtil.getRoundedAmount(value, price.get(), getMaxTradeLimit(), tradeCurrencyCode.get(), paymentAccount.getPaymentMethod().getId());
value = CoinUtil.getAdjustedAmountForHalCash(value, price.get(), getMaxTradeLimit());
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
value = CoinUtil.getRoundedFiatAmount(value, price.get(), getMaxTradeLimit());
calculateVolume(); calculateVolume();
@ -680,7 +672,7 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
} }
public boolean isUsingHalCashAccount() { public boolean isUsingRoundedAtmCashAccount() {
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); return PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId());
} }
} }

View File

@ -100,7 +100,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; 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.main.offer.OfferViewUtil.addPayInfoEntry;
import static haveno.desktop.util.FormBuilder.add2ButtonsAfterGroup; import static haveno.desktop.util.FormBuilder.add2ButtonsAfterGroup;
import static haveno.desktop.util.FormBuilder.addAddressTextField; import static haveno.desktop.util.FormBuilder.addAddressTextField;
@ -828,7 +827,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
int marketPriceAvailableValue = model.marketPriceAvailableProperty.get(); int marketPriceAvailableValue = model.marketPriceAvailableProperty.get();
if (marketPriceAvailableValue > -1) { if (marketPriceAvailableValue > -1) {
boolean showPriceToggle = marketPriceAvailableValue == 1 && boolean showPriceToggle = marketPriceAvailableValue == 1 &&
!model.getDataModel().paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); !PaymentMethod.isFixedPriceOnly(model.getDataModel().paymentAccount.getPaymentMethod().getId());
percentagePriceBox.setVisible(showPriceToggle); percentagePriceBox.setVisible(showPriceToggle);
priceTypeToggleButton.setVisible(showPriceToggle); priceTypeToggleButton.setVisible(showPriceToggle);
boolean fixedPriceSelected = !model.getDataModel().getUseMarketBasedPrice().get() || !showPriceToggle; boolean fixedPriceSelected = !model.getDataModel().getUseMarketBasedPrice().get() || !showPriceToggle;

View File

@ -841,12 +841,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
Volume volume = dataModel.getVolume().get(); Volume volume = dataModel.getVolume().get();
if (volume != null) { if (volume != null) {
// For HalCash we want multiple of 10 EUR volume = VolumeUtil.getAdjustedVolume(volume, dataModel.getPaymentAccount().getPaymentMethod().getId());
if (dataModel.isUsingHalCashAccount())
volume = VolumeUtil.getAdjustedVolumeForHalCash(volume);
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
volume = VolumeUtil.getRoundedFiatVolume(volume);
this.volume.set(VolumeUtil.formatVolume(volume)); this.volume.set(VolumeUtil.formatVolume(volume));
} }
@ -1082,10 +1077,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
long maxTradeLimit = dataModel.getMaxTradeLimit(); long maxTradeLimit = dataModel.getMaxTradeLimit();
Price price = dataModel.getPrice().get(); Price price = dataModel.getPrice().get();
if (price != null && price.isPositive()) { if (price != null && price.isPositive()) {
if (dataModel.isUsingHalCashAccount()) amount = CoinUtil.getRoundedAmount(amount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId());
amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit);
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
amount = CoinUtil.getRoundedFiatAmount(amount, price, maxTradeLimit);
} }
dataModel.setAmount(amount); dataModel.setAmount(amount);
if (syncMinAmountWithAmount || if (syncMinAmountWithAmount ||
@ -1106,10 +1098,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
Price price = dataModel.getPrice().get(); Price price = dataModel.getPrice().get();
long maxTradeLimit = dataModel.getMaxTradeLimit(); long maxTradeLimit = dataModel.getMaxTradeLimit();
if (price != null && price.isPositive()) { if (price != null && price.isPositive()) {
if (dataModel.isUsingHalCashAccount()) minAmount = CoinUtil.getRoundedAmount(minAmount, price, maxTradeLimit, tradeCurrencyCode.get(), dataModel.getPaymentAccount().getPaymentMethod().getId());
minAmount = CoinUtil.getAdjustedAmountForHalCash(minAmount, price, maxTradeLimit);
else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get()))
minAmount = CoinUtil.getRoundedFiatAmount(minAmount, price, maxTradeLimit);
} }
dataModel.setMinAmount(minAmount); dataModel.setMinAmount(minAmount);

View File

@ -63,7 +63,6 @@ import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static haveno.core.payment.payload.PaymentMethod.HAL_CASH_ID;
/** /**
* Domain for that UI element. * Domain for that UI element.
@ -376,10 +375,7 @@ class TakeOfferDataModel extends OfferDataModel {
amount.get() != null && amount.get() != null &&
amount.get().compareTo(BigInteger.valueOf(0)) != 0) { amount.get().compareTo(BigInteger.valueOf(0)) != 0) {
Volume volumeByAmount = tradePrice.getVolumeByAmount(amount.get()); Volume volumeByAmount = tradePrice.getVolumeByAmount(amount.get());
if (offer.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID)) volumeByAmount = VolumeUtil.getAdjustedVolume(volumeByAmount, offer.getPaymentMethod().getId());
volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount);
else if (offer.isFiatOffer())
volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount);
volume.set(volumeByAmount); volume.set(volumeByAmount);
@ -491,7 +487,7 @@ class TakeOfferDataModel extends OfferDataModel {
return offer.getSellerSecurityDeposit(); return offer.getSellerSecurityDeposit();
} }
public boolean isUsingHalCashAccount() { public boolean isRoundedForAtmCash() {
return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); return PaymentMethod.isRoundedForAtmCash(paymentAccount.getPaymentMethod().getId());
} }
} }

View File

@ -302,18 +302,19 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
Price tradePrice = dataModel.tradePrice; Price tradePrice = dataModel.tradePrice;
long maxTradeLimit = dataModel.getMaxTradeLimit(); long maxTradeLimit = dataModel.getMaxTradeLimit();
if (dataModel.getPaymentMethod().getId().equals(PaymentMethod.HAL_CASH_ID)) { if (PaymentMethod.isRoundedForAtmCash(dataModel.getPaymentMethod().getId())) {
BigInteger adjustedAmountForHalCash = CoinUtil.getAdjustedAmountForHalCash(dataModel.getAmount().get(), BigInteger adjustedAmountForHalCash = CoinUtil.getRoundedAtmCashAmount(dataModel.getAmount().get(),
tradePrice, tradePrice,
maxTradeLimit); maxTradeLimit);
dataModel.applyAmount(adjustedAmountForHalCash); dataModel.applyAmount(adjustedAmountForHalCash);
amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get())); 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()))) { if (!isAmountEqualMinAmount(dataModel.getAmount().get()) && (!isAmountEqualMaxAmount(dataModel.getAmount().get()))) {
// We only apply the rounding if the amount is variable (minAmount is lower as 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 // Otherwise we could get an amount lower then the minAmount set by rounding
BigInteger roundedAmount = CoinUtil.getRoundedFiatAmount(dataModel.getAmount().get(), tradePrice, BigInteger roundedAmount = dataModel.getOffer().isFiatOffer() ?
maxTradeLimit); CoinUtil.getRoundedFiatAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit) :
CoinUtil.getRoundedTraditionalAmount(dataModel.getAmount().get(), tradePrice, maxTradeLimit);
dataModel.applyAmount(roundedAmount); dataModel.applyAmount(roundedAmount);
} }
amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get())); amount.set(HavenoUtils.formatXmr(dataModel.getAmount().get()));
@ -585,13 +586,15 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
long maxTradeLimit = dataModel.getMaxTradeLimit(); long maxTradeLimit = dataModel.getMaxTradeLimit();
Price price = dataModel.tradePrice; Price price = dataModel.tradePrice;
if (price != null) { if (price != null) {
if (dataModel.isUsingHalCashAccount()) { if (dataModel.isRoundedForAtmCash()) {
amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit); amount = CoinUtil.getRoundedAtmCashAmount(amount, price, maxTradeLimit);
} else if (dataModel.getOffer().isFiatOffer() } else if (dataModel.getOffer().isTraditionalOffer()
&& !isAmountEqualMinAmount(amount) && !isAmountEqualMaxAmount(amount)) { && !isAmountEqualMinAmount(amount) && !isAmountEqualMaxAmount(amount)) {
// We only apply the rounding if the amount is variable (minAmount is lower as 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 // 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); dataModel.applyAmount(amount);

View File

@ -856,6 +856,7 @@ message PaymentAccountPayload {
CelPayAccountPayload cel_pay_account_payload = 37; CelPayAccountPayload cel_pay_account_payload = 37;
MoneseAccountPayload monese_account_payload = 38; MoneseAccountPayload monese_account_payload = 38;
VerseAccountPayload verse_account_payload = 39; VerseAccountPayload verse_account_payload = 39;
CashByAtmAccountPayload cash_by_atm_account_payload = 40;
} }
} }
@ -1112,6 +1113,10 @@ message PayByMailAccountPayload {
string extra_info = 3; string extra_info = 3;
} }
message CashByAtmAccountPayload {
string extra_info = 1;
}
message PromptPayAccountPayload { message PromptPayAccountPayload {
string prompt_pay_id = 1; string prompt_pay_id = 1;
} }