From f39e61115026e27a3552ae3e1a3a6f8e5273b6fa Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:12:09 -0300 Subject: [PATCH 01/12] Wrap long i18n strings for use in API Some i18n property values can be used by the API if long strings are wrapped before written as commments to json payment account forms, or written to the CLI console. This change anticipates the addition of the more complex Swift payment method (PR 5672). PR 5672's i18n property value for key "payment.swift.info" will be wrapped and appended to the comments of the Swift payment account's json form. --- .../main/java/bisq/common/util/Utilities.java | 16 +++++++++-- core/src/main/java/bisq/core/locale/Res.java | 28 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java index 3d530b918d2..30e247b2980 100644 --- a/common/src/main/java/bisq/common/util/Utilities.java +++ b/common/src/main/java/bisq/common/util/Utilities.java @@ -52,9 +52,11 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; @@ -337,12 +339,12 @@ public static boolean isCorrectOSArchitecture() { public static void openURI(URI uri) throws IOException { if (!DesktopUtil.browse(uri)) - throw new IOException("Failed to open URI: " + uri.toString()); + throw new IOException("Failed to open URI: " + uri); } public static void openFile(File file) throws IOException { if (!DesktopUtil.open(file)) - throw new IOException("Failed to open file: " + file.toString()); + throw new IOException("Failed to open file: " + file); } public static String getDownloadOfHomeDir() { @@ -480,6 +482,16 @@ public static String toTruncatedString(Object message, int maxLength, boolean re } + public static List toListOfWrappedStrings(String s, int wrapLength) { + StringBuilder sb = new StringBuilder(s); + int i = 0; + while (i + wrapLength < sb.length() && (i = sb.lastIndexOf(" ", i + wrapLength)) != -1) { + sb.replace(i, i + 1, "\n"); + } + String[] splitLine = sb.toString().split("\n"); + return Arrays.asList(splitLine); + } + public static String getRandomPrefix(int minLength, int maxLength) { int length = minLength + new Random().nextInt(maxLength - minLength + 1); String result; diff --git a/core/src/main/java/bisq/core/locale/Res.java b/core/src/main/java/bisq/core/locale/Res.java index fcf9141c918..cec006aff0d 100644 --- a/core/src/main/java/bisq/core/locale/Res.java +++ b/core/src/main/java/bisq/core/locale/Res.java @@ -33,6 +33,8 @@ import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; @@ -42,6 +44,9 @@ import org.jetbrains.annotations.NotNull; +import static bisq.common.util.Utilities.toListOfWrappedStrings; +import static java.nio.charset.StandardCharsets.UTF_8; + @Slf4j public class Res { public static void setup() { @@ -125,13 +130,30 @@ public static String get(String key) { return key; } } + + public static List getWrappedAsList(String key, int wrapLength) { + String[] raw = get(key).split("\n"); + List wrapped = new ArrayList<>(); + for (String s : raw) { + List list = toListOfWrappedStrings(s, wrapLength); + for (String line : list) { + if (!line.isEmpty()) + wrapped.add(line); + } + } + return wrapped; + } } // Adds UTF8 support for property files class UTF8Control extends ResourceBundle.Control { - public ResourceBundle newBundle(String baseName, @NotNull Locale locale, @NotNull String format, ClassLoader loader, boolean reload) - throws IllegalAccessException, InstantiationException, IOException { + public ResourceBundle newBundle(String baseName, + @NotNull Locale locale, + @NotNull String format, + ClassLoader loader, + boolean reload) + throws IOException { // Below is a copy of the default implementation. final String bundleName = toBundleName(baseName, locale); final String resourceName = toResourceName(bundleName, "properties"); @@ -152,7 +174,7 @@ public ResourceBundle newBundle(String baseName, @NotNull Locale locale, @NotNul if (stream != null) { try { // Only this line is changed to make it read properties files as UTF-8. - bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8")); + bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8)); } finally { stream.close(); } From 80a72dc5201d07c9fc02534b587078df581553be Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:13:16 -0300 Subject: [PATCH 02/12] Format comments, add one --- .../main/java/bisq/core/api/model/PaymentAccountForm.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java b/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java index eb3a6cd3bac..5de59e7c0f0 100644 --- a/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java +++ b/core/src/main/java/bisq/core/api/model/PaymentAccountForm.java @@ -134,11 +134,11 @@ public class PaymentAccountForm { "maxTradePeriod", "paymentAccountPayload", "paymentMethod", - "paymentMethodId", // This field will be included, but handled differently. - "selectedTradeCurrency", - "tradeCurrencies", // This field may be included, but handled differently. + "paymentMethodId", // Will be included, but handled differently. + "selectedTradeCurrency", // May be included, but handled differently. + "tradeCurrencies", // May be included, but handled differently. "HOLDER_NAME", - "SALT" // This field will be included, but handled differently. + "SALT" // Will be included, but handled differently. }; /** From de23fc00838565a3e082dde2da2131404f863427 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:17:16 -0300 Subject: [PATCH 03/12] Replace growing # of instanceof checks with boolean hasPaymentMethodWithId --- .../bisq/core/payment/PaymentAccount.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java index caafed4611f..948b32657e2 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccount.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java @@ -41,6 +41,7 @@ import javax.annotation.Nullable; +import static bisq.core.payment.payload.PaymentMethod.*; import static com.google.common.base.Preconditions.checkNotNull; @EqualsAndHashCode @@ -104,14 +105,14 @@ public static PaymentAccount fromProto(protobuf.PaymentAccount proto, CoreProtoR // We need to remove NGN for Transferwise Optional ngnTwOptional = tradeCurrencies.stream() - .filter(e -> paymentMethodId.equals(PaymentMethod.TRANSFERWISE_ID)) + .filter(e -> paymentMethodId.equals(TRANSFERWISE_ID)) .filter(e -> e.getCode().equals("NGN")) .findAny(); // We cannot remove it in the stream as it would cause a concurrentModificationException ngnTwOptional.ifPresent(tradeCurrencies::remove); try { - PaymentAccount account = PaymentAccountFactory.getPaymentAccount(PaymentMethod.getPaymentMethodById(paymentMethodId)); + PaymentAccount account = PaymentAccountFactory.getPaymentAccount(getPaymentMethodById(paymentMethodId)); account.getTradeCurrencies().clear(); account.setId(proto.getId()); account.setCreationDate(proto.getCreationDate()); @@ -151,6 +152,19 @@ public boolean hasMultipleCurrencies() { return tradeCurrencies.size() > 1; } + public boolean canSupportMultipleCurrencies() { + return this.hasPaymentMethodWithId(ADVANCED_CASH_ID) + || this.hasPaymentMethodWithId(AMAZON_GIFT_CARD_ID) + || this.hasPaymentMethodWithId(CAPITUAL_ID) + || this.hasPaymentMethodWithId(MONEY_GRAM_ID) + || this.hasPaymentMethodWithId(PAYSERA_ID) + || this.hasPaymentMethodWithId(PAXUM_ID) + || this.hasPaymentMethodWithId(REVOLUT_ID) + // || this.hasPaymentMethodWithId(SWIFT_ID) + || this.hasPaymentMethodWithId(TRANSFERWISE_ID) + || this.hasPaymentMethodWithId(UPHOLD_ID); + } + public void setSingleTradeCurrency(TradeCurrency tradeCurrency) { tradeCurrencies.clear(); tradeCurrencies.add(tradeCurrency); @@ -195,23 +209,15 @@ public boolean isCountryBasedPaymentAccount() { return this instanceof CountryBasedPaymentAccount; } - public boolean isHalCashAccount() { - return this instanceof HalCashAccount; - } - - public boolean isMoneyGramAccount() { - return this instanceof MoneyGramAccount; - } - - public boolean isTransferwiseAccount() { - return this instanceof TransferwiseAccount; + public boolean hasPaymentMethodWithId(String paymentMethodId) { + return this.getPaymentMethod().getId().equals(paymentMethodId); } /** * Return an Optional of the trade currency for this payment account, or * Optional.empty() if none is found. If this payment account has a selected * trade currency, that is returned, else its single trade currency is returned, - * else the first trade currency in the this payment account's tradeCurrencies + * else the first trade currency in this payment account's tradeCurrencies * list is returned. * * @return Optional of the trade currency for the given payment account From e00cc86fead453ecabf9e65be9f7726a57e8fb1c Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:18:51 -0300 Subject: [PATCH 04/12] Adjust to PaymentAccount changes in commit de23fc0 --- .../main/offer/MutableOfferDataModel.java | 13 ++++---- .../desktop/main/offer/MutableOfferView.java | 5 +-- .../main/offer/MutableOfferViewModel.java | 31 ++++++------------- .../offer/takeoffer/TakeOfferDataModel.java | 5 +-- .../offer/takeoffer/TakeOfferViewModel.java | 14 ++++----- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java index b96376d10af..3f2a733168b 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferDataModel.java @@ -92,6 +92,7 @@ import javax.annotation.Nullable; +import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Comparator.comparing; @@ -565,7 +566,7 @@ private Volume calculateVolumeForAmount(ObjectProperty minAmount) { Volume volumeByAmount = price.get().getVolumeByAmount(minAmount.get()); // For HalCash we want multiple of 10 EUR - if (paymentAccount.isHalCashAccount()) + if (isUsingHalCashAccount()) volumeByAmount = VolumeUtil.getAdjustedVolumeForHalCash(volumeByAmount); else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get())) volumeByAmount = VolumeUtil.getRoundedFiatVolume(volumeByAmount); @@ -576,7 +577,7 @@ void calculateAmount() { if (isNonZeroPrice.test(price) && isNonZeroVolume.test(volume) && allowAmountUpdate) { try { Coin value = DisplayUtils.reduceTo4Decimals(price.get().getAmountByVolume(volume.get()), btcFormatter); - if (paymentAccount.isHalCashAccount()) + if (isUsingHalCashAccount()) value = CoinUtil.getAdjustedAmountForHalCash(value, price.get(), getMaxTradeLimit()); else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get())) value = CoinUtil.getRoundedFiatAmount(value, price.get(), getMaxTradeLimit()); @@ -647,10 +648,6 @@ protected void setBuyerSecurityDeposit(double value) { this.buyerSecurityDeposit.set(value); } - protected boolean isUseMarketBasedPriceValue() { - return marketPriceAvailable && useMarketBasedPrice.get() && !paymentAccount.isHalCashAccount(); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @@ -782,4 +779,8 @@ public boolean isMinBuyerSecurityDeposit() { public void setTriggerPrice(long triggerPrice) { this.triggerPrice = triggerPrice; } + + public boolean isUsingHalCashAccount() { + return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index 3794dc1b63f..0835919d7c9 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -117,6 +117,7 @@ import org.jetbrains.annotations.NotNull; +import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID; import static bisq.desktop.util.FormBuilder.*; import static javafx.beans.binding.Bindings.createStringBinding; @@ -345,7 +346,7 @@ private void onPlaceOffer() { Offer offer = model.createAndGetOffer(); if (!DevEnv.isDevMode()) { offerDetailsWindow.onPlaceOffer(() -> - model.onPlaceOffer(offer, offerDetailsWindow::hide)) + model.onPlaceOffer(offer, offerDetailsWindow::hide)) .show(offer); } else { balanceSubscription.unsubscribe(); @@ -881,7 +882,7 @@ protected void updatePriceToggle() { int marketPriceAvailableValue = model.marketPriceAvailableProperty.get(); if (marketPriceAvailableValue > -1) { boolean showPriceToggle = marketPriceAvailableValue == 1 && - !model.getDataModel().paymentAccount.isHalCashAccount(); + !model.getDataModel().paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); percentagePriceBox.setVisible(showPriceToggle); priceTypeToggleButton.setVisible(showPriceToggle); boolean fixedPriceSelected = !model.getDataModel().getUseMarketBasedPrice().get() || !showPriceToggle; diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index 13912e055c9..952197daea1 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -122,7 +122,7 @@ public abstract class MutableOfferViewModel ext final StringProperty buyerSecurityDepositInBTC = new SimpleStringProperty(); final StringProperty buyerSecurityDepositLabel = new SimpleStringProperty(); - // Price in the viewModel is always dependent on fiat/altcoin: Fiat Fiat/BTC, for altcoins we use inverted price. + // Price in the viewModel is always dependent on fiat/altcoin: Fiat/BTC, for altcoins we use inverted price. // The domain (dataModel) uses always the same price model (otherCurrencyBTC) // If we would change the price representation in the domain we would not be backward compatible public final StringProperty price = new SimpleStringProperty(); @@ -364,7 +364,7 @@ private void createListeners() { double percentage = ParsingUtils.parsePercentStringToDouble(newValue); if (percentage >= 1 || percentage <= -1) { new Popup().warning(Res.get("popup.warning.tooLargePercentageValue") + "\n" + - Res.get("popup.warning.examplePercentageValue")) + Res.get("popup.warning.examplePercentageValue")) .show(); } else { final String currencyCode = dataModel.getTradeCurrencyCode().get(); @@ -492,11 +492,7 @@ private void createListeners() { } }; - isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState(); - /* feeFromFundingTxListener = (ov, oldValue, newValue) -> { - updateButtonDisableState(); - };*/ currenciesUpdateListener = (observable, oldValue, newValue) -> { updateMarketPriceAvailable(); @@ -691,8 +687,8 @@ boolean fundFromSavingsWallet() { return true; } else { new Popup().warning(Res.get("shared.notEnoughFunds", - btcFormatter.formatCoinWithCode(dataModel.totalToPayAsCoinProperty().get()), - btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) + btcFormatter.formatCoinWithCode(dataModel.totalToPayAsCoinProperty().get()), + btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) .actionButtonTextWithGoTo("navigation.funds.depositFunds") .onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class)) .show(); @@ -732,8 +728,8 @@ void onFocusOutAmountTextField(boolean oldValue, boolean newValue) { } else if (amount.get() != null && btcValidator.getMaxTradeLimit() != null && btcValidator.getMaxTradeLimit().value == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value) { amount.set(btcFormatter.formatCoin(btcValidator.getMaxTradeLimit())); new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", - btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), - Res.get("offerbook.warning.newVersionAnnouncement"))) + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) .width(900) .show(); } @@ -882,7 +878,7 @@ void onFocusOutVolumeTextField(boolean oldValue, boolean newValue) { Volume volume = dataModel.getVolume().get(); if (volume != null) { // For HalCash we want multiple of 10 EUR - if (dataModel.paymentAccount.isHalCashAccount()) + if (dataModel.isUsingHalCashAccount()) volume = VolumeUtil.getAdjustedVolumeForHalCash(volume); else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get())) volume = VolumeUtil.getRoundedFiatVolume(volume); @@ -974,7 +970,7 @@ public boolean isPriceInRange() { private void displayPriceOutOfRangePopup() { Popup popup = new Popup(); popup.warning(Res.get("createOffer.priceOutSideOfDeviation", - FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()))) + FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()))) .actionButtonText(Res.get("createOffer.changePrice")) .onAction(popup::hide) .closeButtonTextWithGoTo("navigation.settings.preferences") @@ -1161,7 +1157,7 @@ private void setAmountToModel() { long maxTradeLimit = dataModel.getMaxTradeLimit(); Price price = dataModel.getPrice().get(); if (price != null) { - if (dataModel.paymentAccount.isHalCashAccount()) + if (dataModel.isUsingHalCashAccount()) amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit); else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get())) amount = CoinUtil.getRoundedFiatAmount(amount, price, maxTradeLimit); @@ -1185,7 +1181,7 @@ private void setMinAmountToModel() { Price price = dataModel.getPrice().get(); long maxTradeLimit = dataModel.getMaxTradeLimit(); if (price != null) { - if (dataModel.paymentAccount.isHalCashAccount()) + if (dataModel.isUsingHalCashAccount()) minAmount = CoinUtil.getAdjustedAmountForHalCash(minAmount, price, maxTradeLimit); else if (CurrencyUtil.isFiatCurrency(tradeCurrencyCode.get())) minAmount = CoinUtil.getRoundedFiatAmount(minAmount, price, maxTradeLimit); @@ -1281,11 +1277,6 @@ private void updateSpinnerInfo() { waitingForFundsText.set(""); } else if (dataModel.getIsBtcWalletFunded().get()) { waitingForFundsText.set(""); - /* if (dataModel.isFeeFromFundingTxSufficient.get()) { - spinnerInfoText.set(""); - } else { - spinnerInfoText.set("Check if funding tx miner fee is sufficient..."); - }*/ } else { waitingForFundsText.set(Res.get("shared.waitingForFunds")); } @@ -1325,8 +1316,6 @@ void updateButtonDisableState() { } isNextButtonDisabled.set(!inputDataValid); - // boolean notSufficientFees = dataModel.isWalletFunded.get() && dataModel.isMainNet.get() && !dataModel.isFeeFromFundingTxSufficient.get(); - //isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || notSufficientFees); isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || !dataModel.getIsBtcWalletFunded().get()); } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index 00309dc6f8a..b9d2fdd0e12 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -77,6 +77,7 @@ import javax.annotation.Nullable; +import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -678,8 +679,8 @@ public Coin getUsableBsqBalance() { return usableBsqBalance; } - public boolean isHalCashAccount() { - return paymentAccount.isHalCashAccount(); + public boolean isUsingHalCashAccount() { + return paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); } public boolean isCurrencyForTakerFeeBtc() { diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java index 2e20113243f..6c13c24c236 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferViewModel.java @@ -263,8 +263,8 @@ boolean fundFromSavingsWallet() { return true; } else { new Popup().warning(Res.get("shared.notEnoughFunds", - btcFormatter.formatCoinWithCode(dataModel.getTotalToPayAsCoin().get()), - btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) + btcFormatter.formatCoinWithCode(dataModel.getTotalToPayAsCoin().get()), + btcFormatter.formatCoinWithCode(dataModel.getTotalAvailableBalance()))) .actionButtonTextWithGoTo("navigation.funds.depositFunds") .onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class)) .show(); @@ -349,14 +349,14 @@ void onFocusOutAmountTextField(boolean oldValue, boolean newValue, String userIn } else if (btcValidator.getMaxTradeLimit() != null && btcValidator.getMaxTradeLimit().value == OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value) { if (dataModel.getDirection() == OfferPayload.Direction.BUY) { new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.seller", - btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), - Res.get("offerbook.warning.newVersionAnnouncement"))) + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) .width(900) .show(); } else { new Popup().information(Res.get("popup.warning.tradeLimitDueAccountAgeRestriction.buyer", - btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), - Res.get("offerbook.warning.newVersionAnnouncement"))) + btcFormatter.formatCoinWithCode(OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT), + Res.get("offerbook.warning.newVersionAnnouncement"))) .width(900) .show(); } @@ -611,7 +611,7 @@ private void setAmountToModel() { long maxTradeLimit = dataModel.getMaxTradeLimit(); Price price = dataModel.tradePrice; if (price != null) { - if (dataModel.isHalCashAccount()) { + if (dataModel.isUsingHalCashAccount()) { amount = CoinUtil.getAdjustedAmountForHalCash(amount, price, maxTradeLimit); } else if (CurrencyUtil.isFiatCurrency(dataModel.getCurrencyCode()) && !isAmountEqualMinAmount(amount) && !isAmountEqualMaxAmount(amount)) { From 19e9c78f51cdd463230b866566c3bc1000e31b9d Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:21:29 -0300 Subject: [PATCH 05/12] Do single trade currency check in api generated payment acct --- .../java/bisq/core/api/CorePaymentAccountsService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index 220205fbee3..57d914b634d 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -138,9 +138,12 @@ List getCryptoCurrencyPaymentMethods() { } private void verifyPaymentAccountHasRequiredFields(PaymentAccount paymentAccount) { - // Do checks here to make sure required fields are populated. - if (paymentAccount.isTransferwiseAccount() && paymentAccount.getTradeCurrencies().isEmpty()) + if (paymentAccount.canSupportMultipleCurrencies() && paymentAccount.getTradeCurrencies().isEmpty()) throw new IllegalArgumentException(format("no trade currencies defined for %s payment account", paymentAccount.getPaymentMethod().getDisplayString().toLowerCase())); + + if (!paymentAccount.canSupportMultipleCurrencies() && paymentAccount.getSingleTradeCurrency() == null) + throw new IllegalArgumentException(format("no trade currency defined for %s payment account", + paymentAccount.getPaymentMethod().getDisplayString().toLowerCase())); } } From a2bc999692e389776f62ef5d3be165e22d16739f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:23:56 -0300 Subject: [PATCH 06/12] Adjust to PaymentAccount change in commit de23fc0 --- core/src/main/java/bisq/core/offer/CreateOfferService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/CreateOfferService.java index 733eee1a7f9..9f5f61dd68b 100644 --- a/core/src/main/java/bisq/core/offer/CreateOfferService.java +++ b/core/src/main/java/bisq/core/offer/CreateOfferService.java @@ -54,6 +54,8 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.core.payment.payload.PaymentMethod.HAL_CASH_ID; + @Slf4j @Singleton public class CreateOfferService { @@ -133,7 +135,7 @@ public Offer createAndGetOffer(String offerId, NodeAddress makerAddress = p2PService.getAddress(); boolean useMarketBasedPriceValue = useMarketBasedPrice && isMarketPriceAvailable(currencyCode) && - !paymentAccount.isHalCashAccount(); + !paymentAccount.hasPaymentMethodWithId(HAL_CASH_ID); long priceAsLong = price != null && !useMarketBasedPriceValue ? price.getValue() : 0L; double marketPriceMarginParam = useMarketBasedPriceValue ? marketPriceMargin : 0; From f7e68980776863551671270e402c1ee28b1e064b Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:24:51 -0300 Subject: [PATCH 07/12] Let users specify trade ccys and selected trade ccy in json form Several payment methods support multiple trade currencies and a selected trade currency, but the api's payment account creation has not let CLI users specify them in the json form passed to the `createpaymentacct` command. This change adds `tradeCurrencies` and `selectedTradeCurrency` fields to the appropriate json forms. --- .../api/model/PaymentAccountTypeAdapter.java | 141 ++++++++++++++---- 1 file changed, 108 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java index 219c3db04c9..1097cf0526e 100644 --- a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java +++ b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java @@ -53,10 +53,8 @@ import static bisq.common.util.ReflectionUtils.*; import static bisq.common.util.Utilities.decodeFromHex; import static bisq.core.locale.CountryUtil.findCountryByCode; -import static bisq.core.locale.CurrencyUtil.getAllTransferwiseCurrencies; -import static bisq.core.locale.CurrencyUtil.getCurrencyByCountryCode; -import static bisq.core.locale.CurrencyUtil.getTradeCurrencies; -import static bisq.core.locale.CurrencyUtil.getTradeCurrenciesInList; +import static bisq.core.locale.CurrencyUtil.*; +import static bisq.core.payment.payload.PaymentMethod.*; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static java.util.Arrays.stream; @@ -110,13 +108,7 @@ public void write(JsonWriter out, PaymentAccount account) throws IOException { // We're not serializing a real payment account instance here. out.beginObject(); - // All json forms start with immutable _COMMENTS_ and paymentMethodId fields. - out.name("_COMMENTS_"); - out.beginArray(); - for (String s : JSON_COMMENTS) { - out.value(s); - } - out.endArray(); + writeComments(out, account); out.name("paymentMethodId"); out.value(account.getPaymentMethod().getId()); @@ -131,9 +123,32 @@ public void write(JsonWriter out, PaymentAccount account) throws IOException { out.endObject(); } + private void writeComments(JsonWriter out, PaymentAccount account) throws IOException { + // All json forms start with immutable _COMMENTS_ and paymentMethodId fields. + out.name("_COMMENTS_"); + out.beginArray(); + for (String s : JSON_COMMENTS) { + out.value(s); + } + /* + if (account.isSwiftAccount()) { + // Add extra comments for more complex swift account form. + List wrappedSwiftComments = Res.getWrappedAsList("payment.swift.info", 110); + for (String line : wrappedSwiftComments) { + out.value(line); + } + out.value("See https://bisq.wiki/SWIFT"); + } + */ + out.endArray(); + } + + private void writeInnerMutableFields(JsonWriter out, PaymentAccount account) { - if (account.isTransferwiseAccount()) + if (account.canSupportMultipleCurrencies()) { writeTradeCurrenciesField(out, account); + writeSelectedTradeCurrencyField(out, account); + } fieldSettersMap.forEach((field, value) -> { try { @@ -170,7 +185,7 @@ private void writeTradeCurrenciesField(JsonWriter out, PaymentAccount account) { String fieldName = "tradeCurrencies"; log.debug("Append form with non-settable field: {}", fieldName); out.name(fieldName); - out.value("comma delimited currency code list, e.g., gbp,eur"); + out.value("comma delimited currency code list, e.g., gbp,eur,jpy,usd"); } catch (Exception ex) { String errMsg = format("cannot create a new %s json form", account.getClass().getSimpleName()); @@ -179,6 +194,22 @@ private void writeTradeCurrenciesField(JsonWriter out, PaymentAccount account) { } } + // PaymentAccounts that support multiple 'tradeCurrencies' need to define a + // 'selectedTradeCurrency' field (not simply defaulting to first in list). + // Write this field to the form. + private void writeSelectedTradeCurrencyField(JsonWriter out, PaymentAccount account) { + try { + String fieldName = "selectedTradeCurrency"; + log.debug("Append form with settable field: {}", fieldName); + out.name(fieldName); + out.value("primary trading currency code, e.g., eur"); + } catch (Exception ex) { + String errMsg = format("cannot create a new %s json form", + account.getClass().getSimpleName()); + log.error(StringUtils.capitalize(errMsg) + ".", ex); + throw new IllegalStateException("programmer error: " + errMsg); + } + } @Override public PaymentAccount read(JsonReader in) throws IOException { @@ -187,12 +218,17 @@ public PaymentAccount read(JsonReader in) throws IOException { while (in.hasNext()) { String currentFieldName = in.nextName(); - // The tradeCurrency field is common to all payment account types, + // The tradeCurrencies field is common to all payment account types, // but has no setter. if (didReadTradeCurrenciesField(in, account, currentFieldName)) continue; - // Some of the fields are common to all payment account types. + // The selectedTradeCurrency field is common to all payment account types, + // but is @Nullable, and may not need to be explicitly defined by user. + if (didReadSelectedTradeCurrencyField(in, account, currentFieldName)) + continue; + + // Some fields are common to all payment account types. if (didReadCommonField(in, account, currentFieldName)) continue; @@ -318,30 +354,22 @@ private boolean didReadTradeCurrenciesField(JsonReader in, PaymentAccount account, String fieldName) { // The PaymentAccount.tradeCurrencies field is a special case because it has - // no setter, and we add currencies to the List here. Normally, it is an - // excluded field, TransferwiseAccount excepted. + // no setter, so we add currencies to the List here if the payment account + // supports multiple trade currencies. if (fieldName.equals("tradeCurrencies")) { String fieldValue = nextStringOrNull(in); List currencyCodes = commaDelimitedCodesToList.apply(fieldValue); - - Optional> tradeCurrencies; - if (account.isTransferwiseAccount()) - tradeCurrencies = getTradeCurrenciesInList(currencyCodes, getAllTransferwiseCurrencies()); - else - tradeCurrencies = getTradeCurrencies(currencyCodes); - + Optional> tradeCurrencies = getReconciledTradeCurrencies(currencyCodes, account); if (tradeCurrencies.isPresent()) { for (TradeCurrency tradeCurrency : tradeCurrencies.get()) { account.addCurrency(tradeCurrency); } - // For api users, define a selected currency. - account.setSelectedTradeCurrency(account.getTradeCurrency().orElse(null)); } else { // Log a warning. We should not throw an exception here because the - // gson library will not pass it up to the calling Bisq class as it - // would be defined here. Do a check in a calling class to make sure - // the tradeCurrencies field is populated in the PaymentAccount - // object, if it is required for the payment account method. + // gson library will not pass it up to the calling Bisq object exactly as + // it would be defined here (causing confusion). Do a check in a calling + // class to make sure the tradeCurrencies field is populated in the + // PaymentAccount object, if it is required for the payment account method. log.warn("No trade currencies were found in the {} account form.", account.getPaymentMethod().getDisplayString()); } @@ -350,14 +378,61 @@ private boolean didReadTradeCurrenciesField(JsonReader in, return false; } + private Optional> getReconciledTradeCurrencies(List currencyCodes, + PaymentAccount account) { + if (account.hasPaymentMethodWithId(ADVANCED_CASH_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllAdvancedCashCurrencies()); + else if (account.hasPaymentMethodWithId(AMAZON_GIFT_CARD_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllAmazonGiftCardCurrencies()); + else if (account.hasPaymentMethodWithId(CAPITUAL_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllCapitualCurrencies()); + else if (account.hasPaymentMethodWithId(MONEY_GRAM_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllMoneyGramCurrencies()); + else if (account.hasPaymentMethodWithId(PAXUM_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllPaxumCurrencies()); + else if (account.hasPaymentMethodWithId(PAYSERA_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllPayseraCurrencies()); + else if (account.hasPaymentMethodWithId(REVOLUT_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllRevolutCurrencies()); + /*else if (account.hasPaymentMethodWithId(SWIFT_ID)) + return getTradeCurrenciesInList(currencyCodes, new ArrayList<>(getAllSortedFiatCurrencies()));*/ + else if (account.hasPaymentMethodWithId(TRANSFERWISE_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllTransferwiseCurrencies()); + else if (account.hasPaymentMethodWithId(UPHOLD_ID)) + return getTradeCurrenciesInList(currencyCodes, getAllUpholdCurrencies()); + else + return Optional.empty(); + } + + private boolean didReadSelectedTradeCurrencyField(JsonReader in, + PaymentAccount account, + String fieldName) { + if (fieldName.equals("selectedTradeCurrency")) { + String fieldValue = nextStringOrNull(in); + if (fieldValue != null && !fieldValue.isEmpty()) { + Optional tradeCurrency = getTradeCurrency(fieldValue.toUpperCase()); + if (tradeCurrency.isPresent()) { + account.setSelectedTradeCurrency(tradeCurrency.get()); + } else { + // Log an error. We should not throw an exception here because the + // gson library will not pass it up to the calling Bisq object exactly as + // it would be defined here (causing confusion). + log.error("{} is not a valid trade currency code.", fieldValue); + } + } + return true; + } + return false; + } + private boolean didReadCommonField(JsonReader in, PaymentAccount account, String fieldName) throws IOException { switch (fieldName) { case "_COMMENTS_": case "paymentMethodId": - // Skip over the the comments and paymentMethodId, which is already - // set on the PaymentAccount instance. + // Skip over comments and paymentMethodId field, which + // are already set on the PaymentAccount instance. in.skipValue(); return true; case "accountName": @@ -388,7 +463,7 @@ private boolean didReadCountryField(JsonReader in, PaymentAccount account, Strin ((CountryBasedPaymentAccount) account).setCountry(country.get()); FiatCurrency fiatCurrency = getCurrencyByCountryCode(checkNotNull(countryCode)); account.setSingleTradeCurrency(fiatCurrency); - } else if (account.isMoneyGramAccount()) { + } else if (account.hasPaymentMethodWithId(MONEY_GRAM_ID)) { ((MoneyGramAccount) account).setCountry(country.get()); } else { String errMsg = format("cannot set the country on a %s", From 1adde70c7ac3e5efae6280f16cfbd89fd684b070 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:27:39 -0300 Subject: [PATCH 08/12] Do pay acct ccy checks, add tests, anticipate swift pay method Define and verify trade currencies and selected trade currency values in appropriate api test cases, and add test cases for new payment methods. --- .../payment/AbstractPaymentAccountTest.java | 75 ++++++- .../payment/CreatePaymentAccountTest.java | 212 +++++++++++++++++- .../apitest/scenario/PaymentAccountTest.java | 7 +- apitest/src/test/resources/logback.xml | 20 ++ 4 files changed, 299 insertions(+), 15 deletions(-) create mode 100644 apitest/src/test/resources/logback.xml diff --git a/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java index 433898731e5..483371e03da 100644 --- a/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java @@ -1,6 +1,7 @@ package bisq.apitest.method.payment; import bisq.core.api.model.PaymentAccountForm; +import bisq.core.locale.FiatCurrency; import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.payment.PaymentAccount; @@ -17,10 +18,13 @@ import java.io.OutputStreamWriter; import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -57,14 +61,23 @@ public class AbstractPaymentAccountTest extends MethodTest { static final String PROPERTY_NAME_BANK_ACCOUNT_NAME = "bankAccountName"; static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber"; static final String PROPERTY_NAME_BANK_ACCOUNT_TYPE = "bankAccountType"; + static final String PROPERTY_NAME_BANK_ADDRESS = "bankAddress"; + static final String PROPERTY_NAME_BANK_BRANCH = "bankBranch"; static final String PROPERTY_NAME_BANK_BRANCH_CODE = "bankBranchCode"; static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName"; static final String PROPERTY_NAME_BANK_CODE = "bankCode"; + static final String PROPERTY_NAME_BANK_COUNTRY_CODE = "bankCountryCode"; @SuppressWarnings("unused") static final String PROPERTY_NAME_BANK_ID = "bankId"; static final String PROPERTY_NAME_BANK_NAME = "bankName"; + static final String PROPERTY_NAME_BANK_SWIFT_CODE = "bankSwiftCode"; static final String PROPERTY_NAME_BRANCH_ID = "branchId"; static final String PROPERTY_NAME_BIC = "bic"; + static final String PROPERTY_NAME_BENEFICIARY_NAME = "beneficiaryName"; + static final String PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR = "beneficiaryAccountNr"; + static final String PROPERTY_NAME_BENEFICIARY_ADDRESS = "beneficiaryAddress"; + static final String PROPERTY_NAME_BENEFICIARY_CITY = "beneficiaryCity"; + static final String PROPERTY_NAME_BENEFICIARY_PHONE = "beneficiaryPhone"; static final String PROPERTY_NAME_COUNTRY = "country"; static final String PROPERTY_NAME_CITY = "city"; static final String PROPERTY_NAME_CONTACT = "contact"; @@ -75,6 +88,11 @@ public class AbstractPaymentAccountTest extends MethodTest { static final String PROPERTY_NAME_HOLDER_NAME = "holderName"; static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId"; static final String PROPERTY_NAME_IBAN = "iban"; + static final String PROPERTY_NAME_INTERMEDIARY_ADDRESS = "intermediaryAddress"; + static final String PROPERTY_NAME_INTERMEDIARY_BRANCH = "intermediaryBranch"; + static final String PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE = "intermediaryCountryCode"; + static final String PROPERTY_NAME_INTERMEDIARY_NAME = "intermediaryName"; + static final String PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE = "intermediarySwiftCode"; static final String PROPERTY_NAME_MOBILE_NR = "mobileNr"; static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId"; static final String PROPERTY_NAME_PAY_ID = "payid"; @@ -83,7 +101,9 @@ public class AbstractPaymentAccountTest extends MethodTest { static final String PROPERTY_NAME_QUESTION = "question"; static final String PROPERTY_NAME_REQUIREMENTS = "requirements"; static final String PROPERTY_NAME_SALT = "salt"; + static final String PROPERTY_NAME_SELECTED_TRADE_CURRENCY = "selectedTradeCurrency"; static final String PROPERTY_NAME_SORT_CODE = "sortCode"; + static final String PROPERTY_NAME_SPECIAL_INSTRUCTIONS = "specialInstructions"; static final String PROPERTY_NAME_STATE = "state"; static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies"; static final String PROPERTY_NAME_USERNAME = "userName"; @@ -110,7 +130,7 @@ protected final File getEmptyForm(TestInfo testInfo, String paymentMethodId) { COMPLETED_FORM_MAP.clear(); File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId); - // A short cut over the API: + // A shortcut over the API: // File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId); log.debug("{} Empty form saved to {}", testName(testInfo), @@ -125,7 +145,13 @@ protected final void verifyEmptyForm(File jsonForm, String paymentMethodId, Stri PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm), Object.class); assertNotNull(emptyForm); - assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS)); + + if (false && paymentMethodId.equals("SWIFT_ID")) { + assertEquals(getSwiftFormComments(), emptyForm.get(PROPERTY_NAME_JSON_COMMENTS)); + } else { + assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS)); + } + assertEquals(paymentMethodId, emptyForm.get(PROPERTY_NAME_PAYMENT_METHOD_ID)); assertEquals("your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME)); for (String field : fields) { @@ -149,6 +175,15 @@ protected final void verifyAccountSingleTradeCurrency(String expectedCurrencyCod assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode()); } + protected final void verifyAccountTradeCurrencies(Collection expectedFiatCurrencies, + PaymentAccount paymentAccount) { + assertNotNull(paymentAccount.getTradeCurrencies()); + List expectedTradeCurrencies = new ArrayList<>() {{ + addAll(expectedFiatCurrencies); + }}; + assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray()); + } + protected final void verifyAccountTradeCurrencies(List expectedTradeCurrencies, PaymentAccount paymentAccount) { assertNotNull(paymentAccount.getTradeCurrencies()); @@ -164,14 +199,44 @@ protected final void verifyUserPayloadHasPaymentAccountWithId(GrpcClient grpcCli assertTrue(paymentAccount.isPresent()); } + protected final String getCompletedFormAsJsonString(List comments) { + File completedForm = fillPaymentAccountForm(comments); + String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm); + log.debug("Completed form: {}", jsonString); + return jsonString; + } + protected final String getCompletedFormAsJsonString() { - File completedForm = fillPaymentAccountForm(); + File completedForm = fillPaymentAccountForm(PROPERTY_VALUE_JSON_COMMENTS); String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm); log.debug("Completed form: {}", jsonString); return jsonString; } - private File fillPaymentAccountForm() { + protected final String getCommaDelimitedFiatCurrencyCodes(Collection fiatCurrencies) { + return fiatCurrencies.stream() + .sorted(TradeCurrency::compareTo) // note: sorted by ccy name, not ccy code + .map(c -> c.getCurrency().getCurrencyCode()) + .collect(Collectors.joining(",")); + } + + protected final String getCommaDelimitedTradeCurrencyCodes(List tradeCurrencies) { + return tradeCurrencies.stream() + .sorted(Comparator.comparing(TradeCurrency::getCode)) // sorted by code + .map(c -> c.getCode()) + .collect(Collectors.joining(",")); + } + + protected final List getSwiftFormComments() { + List comments = new ArrayList<>(); + comments.addAll(PROPERTY_VALUE_JSON_COMMENTS); + // List wrappedSwiftComments = Res.getWrappedAsList("payment.swift.info", 110); + // comments.addAll(wrappedSwiftComments); + // comments.add("See https://bisq.wiki/SWIFT"); + return comments; + } + + private File fillPaymentAccountForm(List comments) { File tmpJsonForm = null; try { tmpJsonForm = File.createTempFile("temp_acct_form_", @@ -182,7 +247,7 @@ private File fillPaymentAccountForm() { writer.name(PROPERTY_NAME_JSON_COMMENTS); writer.beginArray(); - for (String s : PROPERTY_VALUE_JSON_COMMENTS) { + for (String s : comments) { writer.value(s); } writer.endArray(); diff --git a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java index b7eb7f7ebb7..1d7d8e20563 100644 --- a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java @@ -21,6 +21,7 @@ import bisq.core.payment.AdvancedCashAccount; import bisq.core.payment.AliPayAccount; import bisq.core.payment.AustraliaPayid; +import bisq.core.payment.CapitualAccount; import bisq.core.payment.CashDepositAccount; import bisq.core.payment.ClearXchangeAccount; import bisq.core.payment.F2FAccount; @@ -31,7 +32,9 @@ import bisq.core.payment.MoneyBeamAccount; import bisq.core.payment.MoneyGramAccount; import bisq.core.payment.NationalBankAccount; +import bisq.core.payment.PaxumAccount; import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PayseraAccount; import bisq.core.payment.PerfectMoneyAccount; import bisq.core.payment.PopmoneyAccount; import bisq.core.payment.PromptPayAccount; @@ -58,6 +61,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -103,11 +107,18 @@ public void testCreateAdvancedCashAccount(TestInfo testInfo) { COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ADVANCED_CASH_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Advanced Cash Acct"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "0000 1111 2222"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllAdvancedCashCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "RUB"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt")); String jsonString = getCompletedFormAsJsonString(); AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString); verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr()); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex()); @@ -155,6 +166,33 @@ public void testCreateAustraliaPayidAccount(TestInfo testInfo) { print(paymentAccount); } + @Test + public void testCreateCapitualAccount(TestInfo testInfo) { + File emptyForm = getEmptyForm(testInfo, CAPITUAL_ID); + verifyEmptyForm(emptyForm, + CAPITUAL_ID, + PROPERTY_NAME_ACCOUNT_NR); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CAPITUAL_ID); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Capitual Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "1111 2222 3333-4"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllCapitualCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "BRL"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Capitual Acct Salt")); + String jsonString = getCompletedFormAsJsonString(); + CapitualAccount paymentAccount = (CapitualAccount) createPaymentAccount(aliceClient, jsonString); + verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); + verifyAccountTradeCurrencies(getAllCapitualCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); + verifyCommonFormEntries(paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex()); + print(paymentAccount); + } + @Test public void testCreateCashDepositAccount(TestInfo testInfo) { File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID); @@ -443,6 +481,11 @@ public void testCreateMoneyGramAccount(TestInfo testInfo) { PROPERTY_NAME_STATE); COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_GRAM_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Gram Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllMoneyGramCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "INR"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US"); @@ -452,6 +495,8 @@ public void testCreateMoneyGramAccount(TestInfo testInfo) { MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString); verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName()); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); @@ -481,6 +526,58 @@ public void testCreatePerfectMoneyAccount(TestInfo testInfo) { print(paymentAccount); } + @Test + public void testCreatePaxumAccount(TestInfo testInfo) { + File emptyForm = getEmptyForm(testInfo, PAXUM_ID); + verifyEmptyForm(emptyForm, + PAXUM_ID, + PROPERTY_NAME_EMAIL); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAXUM_ID); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paxum Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllPaxumCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "SEK"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); + String jsonString = getCompletedFormAsJsonString(); + PaxumAccount paymentAccount = (PaxumAccount) createPaymentAccount(aliceClient, jsonString); + verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); + verifyAccountTradeCurrencies(getAllPaxumCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); + verifyCommonFormEntries(paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); + print(paymentAccount); + } + + @Test + public void testCreatePayseraAccount(TestInfo testInfo) { + File emptyForm = getEmptyForm(testInfo, PAYSERA_ID); + verifyEmptyForm(emptyForm, + PAYSERA_ID, + PROPERTY_NAME_EMAIL); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAYSERA_ID); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paysera Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllPayseraCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "ZAR"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); + String jsonString = getCompletedFormAsJsonString(); + PayseraAccount paymentAccount = (PayseraAccount) createPaymentAccount(aliceClient, jsonString); + verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); + verifyAccountTradeCurrencies(getAllPayseraCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); + verifyCommonFormEntries(paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); + print(paymentAccount); + } + @Test public void testCreatePopmoneyAccount(TestInfo testInfo) { File emptyForm = getEmptyForm(testInfo, POPMONEY_ID); @@ -531,12 +628,19 @@ public void testCreateRevolutAccount(TestInfo testInfo) { PROPERTY_NAME_USERNAME); COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, REVOLUT_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Revolut Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllRevolutCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "QAR"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); String jsonString = getCompletedFormAsJsonString(); RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString); verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName()); print(paymentAccount); @@ -697,6 +801,65 @@ public void testCreateSpecificBanksAccount(TestInfo testInfo) { print(paymentAccount); } + /* + @Test + public void testCreateSwiftAccount(TestInfo testInfo) { + // https://www.theswiftcodes.com + File emptyForm = getEmptyForm(testInfo, SWIFT_ID); + verifyEmptyForm(emptyForm, + SWIFT_ID, + PROPERTY_NAME_BANK_SWIFT_CODE); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SWIFT_ID); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "IT Swift Acct w/ DE Intermediary"); + String allFiatCodes = getCommaDelimitedFiatCurrencyCodes(getAllSortedFiatCurrencies()); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, allFiatCodes); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "EUR"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_SWIFT_CODE, "PASCITMMFIR"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_COUNTRY_CODE, "IT"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "BANCA MONTE DEI PASCHI DI SIENA S.P.A."); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH, "SUCC. DI FIRENZE"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ADDRESS, "Via dei Pecori, 8, 50123 Firenze FI, Italy"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_NAME, "Vito de' Medici"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR, "0000 1111 2222 3333"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ADDRESS, "Via dei Pecori, 1, 50123 Firenze FI, Italy"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_CITY, "Firenze"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_PHONE, "+39 055 222222"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SPECIAL_INSTRUCTIONS, "N/A"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE, "DEUTDEFFXXX"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE, "DE"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_NAME, "Kosmo Krump"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_ADDRESS, "TAUNUSANLAGE 12, FRANKFURT AM MAIN, 60262"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_BRANCH, "Deutsche Bank Frankfurt F"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swift Acct Salt")); + String jsonString = getCompletedFormAsJsonString(getSwiftFormComments()); + SwiftAccount paymentAccount = (SwiftAccount) createPaymentAccount(aliceClient, jsonString); + verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); + verifyAccountTradeCurrencies(getAllSortedFiatCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); + verifyCommonFormEntries(paymentAccount); + SwiftAccountPayload payload = (SwiftAccountPayload) paymentAccount.getPaymentAccountPayload(); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_SWIFT_CODE), payload.getBankSwiftCode()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_COUNTRY_CODE), payload.getBankCountryCode()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH), payload.getBankBranch()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ADDRESS), payload.getBankAddress()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_NAME), payload.getBeneficiaryName()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR), payload.getBeneficiaryAccountNr()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ADDRESS), payload.getBeneficiaryAddress()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_CITY), payload.getBeneficiaryCity()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_PHONE), payload.getBeneficiaryPhone()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SPECIAL_INSTRUCTIONS), payload.getSpecialInstructions()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE), payload.getIntermediarySwiftCode()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE), payload.getIntermediaryCountryCode()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_NAME), payload.getIntermediaryName()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_BRANCH), payload.getIntermediaryBranch()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_ADDRESS), payload.getIntermediaryAddress()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex()); + print(paymentAccount); + } + */ + @Test public void testCreateSwishAccount(TestInfo testInfo) { File emptyForm = getEmptyForm(testInfo, SWISH_ID); @@ -728,17 +891,16 @@ public void testCreateTransferwiseAccountWith1TradeCurrency(TestInfo testInfo) { PROPERTY_NAME_EMAIL); COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct"); - COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "eur"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "NZD"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "NZD"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); String jsonString = getCompletedFormAsJsonString(); TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString); verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); assertEquals(1, paymentAccount.getTradeCurrencies().size()); - TradeCurrency expectedCurrency = getTradeCurrency("EUR").get(); - assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency()); - List expectedTradeCurrencies = singletonList(expectedCurrency); - verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); print(paymentAccount); @@ -752,7 +914,8 @@ public void testCreateTransferwiseAccountWith10TradeCurrencies(TestInfo testInfo PROPERTY_NAME_EMAIL); COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct"); - COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "ars, cad, hrk, czk, eur, hkd, idr, jpy, chf, nzd"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "ARS,CAD,HRK,CZK,EUR,HKD,IDR,JPY,CHF,NZD"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "CHF"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); String jsonString = getCompletedFormAsJsonString(); @@ -772,8 +935,34 @@ public void testCreateTransferwiseAccountWith10TradeCurrencies(TestInfo testInfo add(getTradeCurrency("NZD").get()); }}; verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount); - TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0); - assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency()); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); + verifyCommonFormEntries(paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); + print(paymentAccount); + } + + @Test + public void testCreateTransferwiseAccountWithSupportedTradeCurrencies(TestInfo testInfo) { + File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID); + verifyEmptyForm(emptyForm, + TRANSFERWISE_ID, + PROPERTY_NAME_EMAIL); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllTransferwiseCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "AUD"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, ""); + String jsonString = getCompletedFormAsJsonString(); + TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString); + verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); + verifyAccountTradeCurrencies(getAllTransferwiseCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail()); print(paymentAccount); @@ -826,11 +1015,18 @@ public void testCreateUpholdAccount(TestInfo testInfo) { COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, UPHOLD_ID); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Uphold Acct"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "UA 9876"); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, getAllUpholdCurrencies() + .stream() + .map(TradeCurrency::getCode) + .collect(Collectors.joining(","))); + COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "MXN"); COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt")); String jsonString = getCompletedFormAsJsonString(); UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString); verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId()); verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount); + assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY), + paymentAccount.getSelectedTradeCurrency().getCode()); verifyCommonFormEntries(paymentAccount); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId()); assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex()); diff --git a/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java index 1aaf553c857..8d03b7dd470 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/PaymentAccountTest.java @@ -49,6 +49,7 @@ public void testCreatePaymentAccount(TestInfo testInfo) { test.testCreateAdvancedCashAccount(testInfo); test.testCreateAliPayAccount(testInfo); test.testCreateAustraliaPayidAccount(testInfo); + test.testCreateCapitualAccount(testInfo); test.testCreateCashDepositAccount(testInfo); test.testCreateBrazilNationalBankAccount(testInfo); test.testCreateClearXChangeAccount(testInfo); @@ -60,6 +61,8 @@ public void testCreatePaymentAccount(TestInfo testInfo) { test.testCreateMoneyBeamAccount(testInfo); test.testCreateMoneyGramAccount(testInfo); test.testCreatePerfectMoneyAccount(testInfo); + test.testCreatePaxumAccount(testInfo); + test.testCreatePayseraAccount(testInfo); test.testCreatePopmoneyAccount(testInfo); test.testCreatePromptPayAccount(testInfo); test.testCreateRevolutAccount(testInfo); @@ -67,12 +70,12 @@ public void testCreatePaymentAccount(TestInfo testInfo) { test.testCreateSepaInstantAccount(testInfo); test.testCreateSepaAccount(testInfo); test.testCreateSpecificBanksAccount(testInfo); + // test.testCreateSwiftAccount(testInfo); test.testCreateSwishAccount(testInfo); - // TransferwiseAccount is only PaymentAccount with a - // tradeCurrencies field in the json form. test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo); test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo); + test.testCreateTransferwiseAccountWithSupportedTradeCurrencies(testInfo); test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo); test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo); diff --git a/apitest/src/test/resources/logback.xml b/apitest/src/test/resources/logback.xml new file mode 100644 index 00000000000..4f5ca59d99d --- /dev/null +++ b/apitest/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + %highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30}: %msg %xEx%n) + + + + + + + + + From ab929d4435bd0df6392a3ff8a8bf3df6ff15b1a5 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 7 Sep 2021 11:31:56 -0300 Subject: [PATCH 09/12] Remove spurious `PaymentAccount#canSupportMultipleCurrencies()` Method was added on the false assumption `PaymentAccount#hasMultipleCurrencies()` would not always return a correct value when a `PaymentAccount` instance is created via reflection. But `hasMultipleCurrencies()` will work as long as appropriate PaymentAccount subclasses continue setting their `tradeCurrencies` fields within their default constructors. --- .../method/payment/CreatePaymentAccountTest.java | 2 +- .../core/api/CorePaymentAccountsService.java | 6 +----- .../api/model/PaymentAccountTypeAdapter.java | 2 +- .../java/bisq/core/payment/PaymentAccount.java | 16 ++-------------- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java index 1d7d8e20563..26e2030c844 100644 --- a/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/method/payment/CreatePaymentAccountTest.java @@ -1002,7 +1002,7 @@ public void testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowExcept Throwable exception = assertThrows(StatusRuntimeException.class, () -> createPaymentAccount(aliceClient, jsonString)); - assertEquals("INVALID_ARGUMENT: no trade currencies defined for transferwise payment account", + assertEquals("INVALID_ARGUMENT: no trade currency defined for transferwise payment account", exception.getMessage()); } diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index 57d914b634d..416dde6531b 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -138,11 +138,7 @@ List getCryptoCurrencyPaymentMethods() { } private void verifyPaymentAccountHasRequiredFields(PaymentAccount paymentAccount) { - if (paymentAccount.canSupportMultipleCurrencies() && paymentAccount.getTradeCurrencies().isEmpty()) - throw new IllegalArgumentException(format("no trade currencies defined for %s payment account", - paymentAccount.getPaymentMethod().getDisplayString().toLowerCase())); - - if (!paymentAccount.canSupportMultipleCurrencies() && paymentAccount.getSingleTradeCurrency() == null) + if (!paymentAccount.hasMultipleCurrencies() && paymentAccount.getSingleTradeCurrency() == null) throw new IllegalArgumentException(format("no trade currency defined for %s payment account", paymentAccount.getPaymentMethod().getDisplayString().toLowerCase())); } diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java index 1097cf0526e..85b2344de0a 100644 --- a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java +++ b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java @@ -145,7 +145,7 @@ private void writeComments(JsonWriter out, PaymentAccount account) throws IOExce private void writeInnerMutableFields(JsonWriter out, PaymentAccount account) { - if (account.canSupportMultipleCurrencies()) { + if (account.hasMultipleCurrencies()) { writeTradeCurrenciesField(out, account); writeSelectedTradeCurrencyField(out, account); } diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java index 948b32657e2..686dc7d972f 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccount.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java @@ -41,7 +41,8 @@ import javax.annotation.Nullable; -import static bisq.core.payment.payload.PaymentMethod.*; +import static bisq.core.payment.payload.PaymentMethod.TRANSFERWISE_ID; +import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById; import static com.google.common.base.Preconditions.checkNotNull; @EqualsAndHashCode @@ -152,19 +153,6 @@ public boolean hasMultipleCurrencies() { return tradeCurrencies.size() > 1; } - public boolean canSupportMultipleCurrencies() { - return this.hasPaymentMethodWithId(ADVANCED_CASH_ID) - || this.hasPaymentMethodWithId(AMAZON_GIFT_CARD_ID) - || this.hasPaymentMethodWithId(CAPITUAL_ID) - || this.hasPaymentMethodWithId(MONEY_GRAM_ID) - || this.hasPaymentMethodWithId(PAYSERA_ID) - || this.hasPaymentMethodWithId(PAXUM_ID) - || this.hasPaymentMethodWithId(REVOLUT_ID) - // || this.hasPaymentMethodWithId(SWIFT_ID) - || this.hasPaymentMethodWithId(TRANSFERWISE_ID) - || this.hasPaymentMethodWithId(UPHOLD_ID); - } - public void setSingleTradeCurrency(TradeCurrency tradeCurrency) { tradeCurrencies.clear(); tradeCurrencies.add(tradeCurrency); From 343b14ec0a7d75be8da4896eba1ff607bf5490f5 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 7 Sep 2021 11:34:48 -0300 Subject: [PATCH 10/12] Set apitest test case log level to INFO --- apitest/src/test/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apitest/src/test/resources/logback.xml b/apitest/src/test/resources/logback.xml index 4f5ca59d99d..28279faa118 100644 --- a/apitest/src/test/resources/logback.xml +++ b/apitest/src/test/resources/logback.xml @@ -12,7 +12,7 @@ - + From 429e943a363d76c6a8953d2905c5ab33dee8f6bc Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 16 Sep 2021 08:57:31 -0300 Subject: [PATCH 11/12] Add TODO commment above placeholder for future swift test Resolves https://github.com/bisq-network/bisq/pull/5685#discussion_r710005619. --- .../bisq/apitest/method/payment/AbstractPaymentAccountTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java index 483371e03da..92b8e8309ca 100644 --- a/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java +++ b/apitest/src/test/java/bisq/apitest/method/payment/AbstractPaymentAccountTest.java @@ -146,6 +146,7 @@ protected final void verifyEmptyForm(File jsonForm, String paymentMethodId, Stri Object.class); assertNotNull(emptyForm); + // TODO remove 'false' condition to enable creation of SWIFT accounts in future PR. if (false && paymentMethodId.equals("SWIFT_ID")) { assertEquals(getSwiftFormComments(), emptyForm.get(PROPERTY_NAME_JSON_COMMENTS)); } else { From 1a9cdfbdd343050de93df2f2db18b7fd6548cf07 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Thu, 16 Sep 2021 09:31:22 -0300 Subject: [PATCH 12/12] Return early when field name match fails Resolves https://github.com/bisq-network/bisq/pull/5685#discussion_r710002786 --- .../api/model/PaymentAccountTypeAdapter.java | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java index 85b2344de0a..00b0654145f 100644 --- a/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java +++ b/core/src/main/java/bisq/core/api/model/PaymentAccountTypeAdapter.java @@ -353,29 +353,29 @@ else if (s != null && !s.isEmpty()) private boolean didReadTradeCurrenciesField(JsonReader in, PaymentAccount account, String fieldName) { + if (!fieldName.equals("tradeCurrencies")) + return false; + // The PaymentAccount.tradeCurrencies field is a special case because it has // no setter, so we add currencies to the List here if the payment account // supports multiple trade currencies. - if (fieldName.equals("tradeCurrencies")) { - String fieldValue = nextStringOrNull(in); - List currencyCodes = commaDelimitedCodesToList.apply(fieldValue); - Optional> tradeCurrencies = getReconciledTradeCurrencies(currencyCodes, account); - if (tradeCurrencies.isPresent()) { - for (TradeCurrency tradeCurrency : tradeCurrencies.get()) { - account.addCurrency(tradeCurrency); - } - } else { - // Log a warning. We should not throw an exception here because the - // gson library will not pass it up to the calling Bisq object exactly as - // it would be defined here (causing confusion). Do a check in a calling - // class to make sure the tradeCurrencies field is populated in the - // PaymentAccount object, if it is required for the payment account method. - log.warn("No trade currencies were found in the {} account form.", - account.getPaymentMethod().getDisplayString()); + String fieldValue = nextStringOrNull(in); + List currencyCodes = commaDelimitedCodesToList.apply(fieldValue); + Optional> tradeCurrencies = getReconciledTradeCurrencies(currencyCodes, account); + if (tradeCurrencies.isPresent()) { + for (TradeCurrency tradeCurrency : tradeCurrencies.get()) { + account.addCurrency(tradeCurrency); } - return true; + } else { + // Log a warning. We should not throw an exception here because the + // gson library will not pass it up to the calling Bisq object exactly as + // it would be defined here (causing confusion). Do a check in a calling + // class to make sure the tradeCurrencies field is populated in the + // PaymentAccount object, if it is required for the payment account method. + log.warn("No trade currencies were found in the {} account form.", + account.getPaymentMethod().getDisplayString()); } - return false; + return true; } private Optional> getReconciledTradeCurrencies(List currencyCodes, @@ -407,22 +407,22 @@ else if (account.hasPaymentMethodWithId(UPHOLD_ID)) private boolean didReadSelectedTradeCurrencyField(JsonReader in, PaymentAccount account, String fieldName) { - if (fieldName.equals("selectedTradeCurrency")) { - String fieldValue = nextStringOrNull(in); - if (fieldValue != null && !fieldValue.isEmpty()) { - Optional tradeCurrency = getTradeCurrency(fieldValue.toUpperCase()); - if (tradeCurrency.isPresent()) { - account.setSelectedTradeCurrency(tradeCurrency.get()); - } else { - // Log an error. We should not throw an exception here because the - // gson library will not pass it up to the calling Bisq object exactly as - // it would be defined here (causing confusion). - log.error("{} is not a valid trade currency code.", fieldValue); - } + if (!fieldName.equals("selectedTradeCurrency")) + return false; + + String fieldValue = nextStringOrNull(in); + if (fieldValue != null && !fieldValue.isEmpty()) { + Optional tradeCurrency = getTradeCurrency(fieldValue.toUpperCase()); + if (tradeCurrency.isPresent()) { + account.setSelectedTradeCurrency(tradeCurrency.get()); + } else { + // Log an error. We should not throw an exception here because the + // gson library will not pass it up to the calling Bisq object exactly as + // it would be defined here (causing confusion). + log.error("{} is not a valid trade currency code.", fieldValue); } - return true; } - return false; + return true; } private boolean didReadCommonField(JsonReader in,