Merge branch 'feature/1285/account-offer-validation' of https://github.com/tau3/exchange into tau3-feature/1285/account-offer-validation

This commit is contained in:
Manfred Karrer 2018-02-01 13:08:47 -05:00
commit 26db1270a5
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
8 changed files with 426 additions and 75 deletions

View file

@ -1,5 +1,8 @@
package io.bisq.core.payment;
import javax.annotation.Nullable;
public interface BankAccount {
@Nullable
String getBankId();
}

View file

@ -1,17 +1,17 @@
package io.bisq.core.payment;
import io.bisq.common.locale.TradeCurrency;
import io.bisq.core.offer.Offer;
import io.bisq.core.payment.payload.PaymentMethod;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class PaymentAccountUtil {
public static boolean isAnyPaymentAccountValidForOffer(Offer offer, Collection<PaymentAccount> paymentAccounts) {
@ -38,77 +38,8 @@ public class PaymentAccountUtil {
"Payment method from offer: " + offer.getPaymentMethod().toString();
}
// TODO not tested with all combinations yet....
// TODO refactor and break up in smaller methods
public static boolean isPaymentAccountValidForOffer(Offer offer, PaymentAccount paymentAccount) {
// check if we have a matching currency
Set<String> paymentAccountCurrencyCodes = paymentAccount.getTradeCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
boolean matchesCurrencyCode = paymentAccountCurrencyCodes.contains(offer.getCurrencyCode());
if (!matchesCurrencyCode)
return false;
// check if we have a matching payment method or if its a bank account payment method which is treated special
final boolean arePaymentMethodsEqual = paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod());
if (!arePaymentMethodsEqual &&
paymentAccount.getPaymentMethod().getId().equals(offer.getPaymentMethod().getId()))
log.warn(getInfoForMismatchingPaymentMethodLimits(offer, paymentAccount));
if (paymentAccount instanceof CountryBasedPaymentAccount) {
CountryBasedPaymentAccount countryBasedPaymentAccount = (CountryBasedPaymentAccount) paymentAccount;
// check if we have a matching country
boolean matchesCountryCodes = offer.getAcceptedCountryCodes() != null && countryBasedPaymentAccount.getCountry() != null &&
offer.getAcceptedCountryCodes().contains(countryBasedPaymentAccount.getCountry().code);
if (!matchesCountryCodes)
return false;
// We have same country
if (countryBasedPaymentAccount instanceof SepaAccount ||
countryBasedPaymentAccount instanceof SepaInstantAccount ||
offer.getPaymentMethod().equals(PaymentMethod.SEPA) ||
offer.getPaymentMethod().equals(PaymentMethod.SEPA_INSTANT)) {
return arePaymentMethodsEqual;
} else if (countryBasedPaymentAccount instanceof BankAccount && (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK) ||
offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS))) {
final List<String> acceptedBankIds = offer.getAcceptedBankIds();
checkNotNull(acceptedBankIds, "offer.getAcceptedBankIds() must not be null");
final String bankId = ((BankAccount) countryBasedPaymentAccount).getBankId();
if (countryBasedPaymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean offerSideMatchesBank = bankId != null && acceptedBankIds.contains(bankId);
boolean paymentAccountSideMatchesBank = ((SpecificBanksAccount) countryBasedPaymentAccount).getAcceptedBanks().contains(offer.getBankId());
return offerSideMatchesBank && paymentAccountSideMatchesBank;
} else {
// national or same bank
return bankId != null && acceptedBankIds.contains(bankId);
}
} else {
if (countryBasedPaymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
final ArrayList<String> acceptedBanks = ((SpecificBanksAccount) countryBasedPaymentAccount).getAcceptedBanks();
return acceptedBanks != null && offer.getBankId() != null && acceptedBanks.contains(offer.getBankId());
} else if (countryBasedPaymentAccount instanceof SameBankAccount) {
// check if we have a matching bank
final String bankId = ((SameBankAccount) countryBasedPaymentAccount).getBankId();
return bankId != null && offer.getBankId() != null && bankId.equals(offer.getBankId());
} else if (countryBasedPaymentAccount instanceof NationalBankAccount) {
return true;
} else if (countryBasedPaymentAccount instanceof WesternUnionAccount) {
return offer.getPaymentMethod().equals(PaymentMethod.WESTERN_UNION);
} else {
log.warn("Not handled case at isPaymentAccountValidForOffer. paymentAccount={}." +
"offer={}",
countryBasedPaymentAccount, offer);
return false;
}
}
} else {
return arePaymentMethodsEqual;
}
return new ReceiptValidator(offer, paymentAccount).isValid();
}
public static Optional<PaymentAccount> getMostMaturePaymentAccountForOffer(Offer offer,

View file

@ -0,0 +1,71 @@
package io.bisq.core.payment;
import io.bisq.common.locale.TradeCurrency;
import io.bisq.core.offer.Offer;
import io.bisq.core.payment.payload.PaymentMethod;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
class ReceiptPredicates {
boolean isEqualPaymentMethods(Offer offer, PaymentAccount account) {
// check if we have a matching payment method or if its a bank account payment method which is treated special
PaymentMethod accountPaymentMethod = account.getPaymentMethod();
PaymentMethod offerPaymentMethod = offer.getPaymentMethod();
boolean arePaymentMethodsEqual = accountPaymentMethod.equals(offerPaymentMethod);
if (log.isWarnEnabled()) {
String accountPaymentMethodId = accountPaymentMethod.getId();
String offerPaymentMethodId = offerPaymentMethod.getId();
if (!arePaymentMethodsEqual && accountPaymentMethodId.equals(offerPaymentMethodId)) {
log.warn(PaymentAccountUtil.getInfoForMismatchingPaymentMethodLimits(offer, account));
}
}
return arePaymentMethodsEqual;
}
boolean isOfferRequireSameOrSpecificBank(Offer offer, PaymentAccount account) {
PaymentMethod paymentMethod = offer.getPaymentMethod();
boolean isSameOrSpecificBank = paymentMethod.equals(PaymentMethod.SAME_BANK)
|| paymentMethod.equals(PaymentMethod.SPECIFIC_BANKS);
return (account instanceof BankAccount) && isSameOrSpecificBank;
}
boolean isMatchingCountryCodes(Offer offer, PaymentAccount account) {
List<String> acceptedCodes = Optional.ofNullable(offer.getAcceptedCountryCodes())
.orElse(Collections.emptyList());
String code = Optional.of(account)
.map(CountryBasedPaymentAccount.class::cast)
.map(CountryBasedPaymentAccount::getCountry)
.map(country -> country.code)
.orElse("undefined");
return acceptedCodes.contains(code);
}
boolean isMatchingCurrency(Offer offer, PaymentAccount account) {
List<TradeCurrency> currencies = account.getTradeCurrencies();
Set<String> codes = currencies.stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
return codes.contains(offer.getCurrencyCode());
}
boolean isSepaRelated(Offer offer, PaymentAccount account) {
PaymentMethod offerPaymentMethod = offer.getPaymentMethod();
return account instanceof SepaAccount
|| account instanceof SepaInstantAccount
|| offerPaymentMethod.equals(PaymentMethod.SEPA)
|| offerPaymentMethod.equals(PaymentMethod.SEPA_INSTANT);
}
}

View file

@ -0,0 +1,92 @@
package io.bisq.core.payment;
import com.google.common.base.Preconditions;
import io.bisq.core.offer.Offer;
import io.bisq.core.payment.payload.PaymentMethod;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
class ReceiptValidator {
private final ReceiptPredicates predicates;
private final PaymentAccount account;
private final Offer offer;
ReceiptValidator(Offer offer, PaymentAccount account) {
this(offer, account, new ReceiptPredicates());
}
ReceiptValidator(Offer offer, PaymentAccount account, ReceiptPredicates predicates) {
this.offer = offer;
this.account = account;
this.predicates = predicates;
}
boolean isValid() {
if (!predicates.isMatchingCurrency(offer, account)) {
return false;
}
boolean isEqualPaymentMethods = predicates.isEqualPaymentMethods(offer, account);
if (!(account instanceof CountryBasedPaymentAccount)) {
return isEqualPaymentMethods;
}
if (!predicates.isMatchingCountryCodes(offer, account)) {
return false;
}
// We have same country
if (predicates.isSepaRelated(offer, account)) {
return isEqualPaymentMethods;
} else if (predicates.isOfferRequireSameOrSpecificBank(offer, account)) {
return isValidWhenOfferRequireSameOrSpecificBank();
} else {
return isValidByType();
}
}
private boolean isValidWhenOfferRequireSameOrSpecificBank() {
final List<String> acceptedBanksForOffer = offer.getAcceptedBankIds();
Preconditions.checkNotNull(acceptedBanksForOffer, "offer.getAcceptedBankIds() must not be null");
final String accountBankId = ((BankAccount) account).getBankId();
if (account instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean offerSideMatchesBank = (accountBankId != null) && acceptedBanksForOffer.contains(accountBankId);
List<String> acceptedBanksForAccount = ((SpecificBanksAccount) account).getAcceptedBanks();
boolean paymentAccountSideMatchesBank = acceptedBanksForAccount.contains(offer.getBankId());
return offerSideMatchesBank && paymentAccountSideMatchesBank;
} else {
// national or same bank
return (accountBankId != null) && acceptedBanksForOffer.contains(accountBankId);
}
}
private boolean isValidByType() {
if (account instanceof SpecificBanksAccount) {
// check if we have a matching bank
final List<String> acceptedBanksForAccount = ((SpecificBanksAccount) account).getAcceptedBanks();
boolean paymentAccountSideMatchesBank = acceptedBanksForAccount.contains(offer.getBankId());
return (offer.getBankId() != null) && paymentAccountSideMatchesBank;
} else if (account instanceof SameBankAccount) {
// check if we have a matching bank
final String accountBankId = ((SameBankAccount) account).getBankId();
return (accountBankId != null) && (offer.getBankId() != null) && accountBankId.equals(offer.getBankId());
} else if (account instanceof NationalBankAccount) {
return true;
} else if (account instanceof WesternUnionAccount) {
PaymentMethod paymentMethod = offer.getPaymentMethod();
return paymentMethod.equals(PaymentMethod.WESTERN_UNION);
} else {
log.warn("Not handled case at isPaymentAccountValidForOffer. paymentAccount={}. offer={}",
account, offer);
return false;
}
}
}

View file

@ -35,6 +35,7 @@ public final class SpecificBanksAccount extends CountryBasedPaymentAccount imple
return new SpecificBanksAccountPayload(paymentMethod.getId(), id);
}
// TODO change to List
public ArrayList<String> getAcceptedBanks() {
return ((SpecificBanksAccountPayload) paymentAccountPayload).getAcceptedBanks();
}

View file

@ -0,0 +1,71 @@
package io.bisq.core.payment;
import com.google.common.collect.Lists;
import io.bisq.common.locale.CryptoCurrency;
import io.bisq.core.offer.Offer;
import io.bisq.core.payment.payload.PaymentMethod;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({NationalBankAccount.class, SepaAccount.class, SepaInstantAccount.class, PaymentMethod.class})
public class ReceiptPredicatesTest {
private final ReceiptPredicates predicates = new ReceiptPredicates();
@Test
public void testIsMatchingCurrency() {
Offer offer = mock(Offer.class);
when(offer.getCurrencyCode()).thenReturn("USD");
PaymentAccount account = mock(PaymentAccount.class);
when(account.getTradeCurrencies()).thenReturn(Lists.newArrayList(
new CryptoCurrency("BTC", "Bitcoin"),
new CryptoCurrency("ETH", "Ether")));
assertFalse(predicates.isMatchingCurrency(offer, account));
}
@Test
public void testIsSepaRelated() {
assertTrue(predicates.isSepaRelated(mock(Offer.class), mock(SepaInstantAccount.class)));
assertTrue(predicates.isSepaRelated(mock(Offer.class), mock(SepaAccount.class)));
}
@Test
public void testIsMatchingCountryCodes() {
CountryBasedPaymentAccount account = mock(CountryBasedPaymentAccount.class);
when(account.getCountry()).thenReturn(null);
assertFalse(predicates.isMatchingCountryCodes(mock(Offer.class), account));
}
@Test
public void testIsSameOrSpecificBank() {
PaymentMethod.SAME_BANK = mock(PaymentMethod.class);
Offer offer = mock(Offer.class);
when(offer.getPaymentMethod()).thenReturn(PaymentMethod.SAME_BANK);
assertTrue(predicates.isOfferRequireSameOrSpecificBank(offer, mock(NationalBankAccount.class)));
}
@Test
public void testIsEqualPaymentMethods() {
final PaymentMethod method = new PaymentMethod("1");
Offer offer = mock(Offer.class);
when(offer.getPaymentMethod()).thenReturn(method);
PaymentAccount account = mock(PaymentAccount.class);
when(account.getPaymentMethod()).thenReturn(method);
assertTrue(predicates.isEqualPaymentMethods(offer, account));
}
}

View file

@ -0,0 +1,170 @@
package io.bisq.core.payment;
import io.bisq.core.offer.Offer;
import io.bisq.core.payment.payload.PaymentMethod;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({SpecificBanksAccount.class, SameBankAccount.class, NationalBankAccount.class,
WesternUnionAccount.class, PaymentMethod.class})
public class ReceiptValidatorTest {
private ReceiptValidator validator;
private PaymentAccount account;
private Offer offer;
private ReceiptPredicates predicates;
@Before
public void setUp() {
this.predicates = mock(ReceiptPredicates.class);
this.account = mock(CountryBasedPaymentAccount.class);
this.offer = mock(Offer.class);
this.validator = new ReceiptValidator(offer, account, predicates);
}
@Test
public void testIsValidWhenCurrencyDoesNotMatch() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(false);
assertFalse(validator.isValid());
verify(predicates).isMatchingCurrency(offer, account);
}
@Test
public void testIsValidWhenNotCountryBasedAccount() {
account = mock(PaymentAccount.class);
assertFalse(account instanceof CountryBasedPaymentAccount);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenNotMatchingCodes() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(false);
assertFalse(validator.isValid());
verify(predicates).isMatchingCountryCodes(offer, account);
}
@Test
public void testIsValidWhenSepaRelated() {
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(false);
when(predicates.isSepaRelated(offer, account)).thenReturn(true);
assertFalse(validator.isValid());
verify(predicates).isSepaRelated(offer, account);
}
@Test
public void testIsValidWhenSpecificBankAccountAndOfferRequireSpecificBank() {
account = mock(SpecificBanksAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify((SpecificBanksAccount) account).getAcceptedBanks();
}
@Test
public void testIsValidWhenSameBankAccountAndOfferRequireSpecificBank() {
account = mock(SameBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(true);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify((BankAccount) account).getBankId();
}
@Test
public void testIsValidWhenSpecificBankAccount() {
account = mock(SpecificBanksAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify((SpecificBanksAccount) account).getAcceptedBanks();
}
@Test
public void testIsValidWhenSameBankAccount() {
account = mock(SameBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertFalse(new ReceiptValidator(offer, account, predicates).isValid());
verify((SameBankAccount) account).getBankId();
}
@Test
public void testIsValidWhenNationalBankAccount() {
account = mock(NationalBankAccount.class);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
}
@Test
public void testIsValidWhenWesternUnionAccount(){
account = mock(WesternUnionAccount.class);
PaymentMethod.WESTERN_UNION = mock(PaymentMethod.class);
when(offer.getPaymentMethod()).thenReturn(PaymentMethod.WESTERN_UNION);
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertTrue(new ReceiptValidator(offer, account, predicates).isValid());
verify(offer).getPaymentMethod();
}
@Test
public void testIsValidWhenWesternIrregularAccount(){
when(predicates.isMatchingCurrency(offer, account)).thenReturn(true);
when(predicates.isEqualPaymentMethods(offer, account)).thenReturn(true);
when(predicates.isMatchingCountryCodes(offer, account)).thenReturn(true);
when(predicates.isSepaRelated(offer, account)).thenReturn(false);
when(predicates.isOfferRequireSameOrSpecificBank(offer, account)).thenReturn(false);
assertFalse(validator.isValid());
}
}

12
pom.xml
View file

@ -223,6 +223,18 @@
<version>1.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>1.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>