Skip to content

Commit

Permalink
Payment account support for the API
Browse files Browse the repository at this point in the history
  • Loading branch information
Bernard Labno committed Jun 19, 2019
1 parent acd687e commit c7fa660
Show file tree
Hide file tree
Showing 79 changed files with 4,135 additions and 69 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/bisq/api/http/HttpApiModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.api.http.service.HttpApiServer;
import bisq.api.http.service.auth.ApiPasswordManager;
import bisq.api.http.service.auth.TokenRegistry;
import bisq.api.http.service.endpoint.PaymentAccountEndpoint;
import bisq.api.http.service.endpoint.UserEndpoint;
import bisq.api.http.service.endpoint.VersionEndpoint;

Expand All @@ -43,6 +44,7 @@ protected void configure() {
bind(HttpApiServer.class).in(Singleton.class);
bind(TokenRegistry.class).in(Singleton.class);
bind(ApiPasswordManager.class).in(Singleton.class);
bind(PaymentAccountEndpoint.class).in(Singleton.class);
bind(UserEndpoint.class).in(Singleton.class);
bind(VersionEndpoint.class).in(Singleton.class);

Expand Down
102 changes: 102 additions & 0 deletions api/src/main/java/bisq/api/http/exceptions/ExceptionMappers.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package bisq.api.http.exceptions;

import bisq.api.http.service.ValidationErrorMessage;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;

import com.google.common.collect.ImmutableList;

import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;



import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import javax.validation.ValidationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import org.eclipse.jetty.io.EofException;
Expand All @@ -20,9 +34,33 @@ private ExceptionMappers() {
public static void register(ResourceConfig environment) {
environment.register(new ExceptionMappers.EofExceptionMapper(), 1);
environment.register(new ExceptionMappers.JsonParseExceptionMapper(), 1);
environment.register(new ExceptionMappers.BisqValidationExceptionMapper());
environment.register(new ExceptionMappers.ExperimentalFeatureExceptionMapper());
environment.register(new ExceptionMappers.InvalidTypeIdExceptionMapper());
environment.register(new ExceptionMappers.NotFoundExceptionMapper());
environment.register(new ExceptionMappers.ValidationExceptionMapper());
environment.register(new ExceptionMappers.UnauthorizedExceptionMapper());
}

private static Response toResponse(Throwable throwable, Response.Status status) {
Response.ResponseBuilder responseBuilder = Response.status(status);
String message = throwable.getMessage();
if (message != null) {
responseBuilder.entity(new ValidationErrorMessage(ImmutableList.of(message)));
}
return responseBuilder.type(MediaType.APPLICATION_JSON).build();
}

public static class BisqValidationExceptionMapper implements ExceptionMapper<bisq.core.exceptions.ValidationException> {
@Override
public Response toResponse(bisq.core.exceptions.ValidationException exception) {
Response.ResponseBuilder responseBuilder = Response.status(422);
String message = exception.getMessage();
responseBuilder.entity(new ValidationErrorMessage(ImmutableList.of(message)));
return responseBuilder.build();
}
}

public static class EofExceptionMapper implements ExceptionMapper<EofException> {
@Override
public Response toResponse(EofException e) {
Expand All @@ -37,6 +75,70 @@ public Response toResponse(JsonParseException e) {
}
}

public static class InvalidTypeIdExceptionMapper implements ExceptionMapper<InvalidTypeIdException> {
@Override
public Response toResponse(InvalidTypeIdException exception) {
Class<?> rawClass = exception.getBaseType().getRawClass();
StringBuilder builder = new StringBuilder("Unable to recognize sub type of ")
.append(rawClass.getSimpleName())
.append(". Value '")
.append(exception.getTypeId())
.append("' is invalid.");

JsonSubTypes annotation = rawClass.getAnnotation(JsonSubTypes.class);
if (annotation != null && annotation.value().length > 0) {
builder.append(" Allowed values are: ");
String separator = ", ";
for (JsonSubTypes.Type subType : annotation.value())
builder.append(subType.name()).append(separator);
builder.delete(builder.length() - separator.length(), builder.length());
}

return Response.status(422).entity(new ValidationErrorMessage(ImmutableList.of(builder.toString()))).build();
}
}

public static class ExperimentalFeatureExceptionMapper implements ExceptionMapper<ExperimentalFeatureException> {
@Override
public Response toResponse(ExperimentalFeatureException exception) {
return ExceptionMappers.toResponse(exception, Response.Status.NOT_IMPLEMENTED);
}
}

public static class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException exception) {
return Response.status(404).entity(exception.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
}
}

public static class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
@Override
public Response toResponse(ValidationException exception) {
Response.ResponseBuilder responseBuilder = Response.status(422);
String message = exception.getMessage();
if (exception instanceof ConstraintViolationException) {
List<String> messages = ((ConstraintViolationException) exception).getConstraintViolations().stream().map(constraintViolation -> {
StringBuilder stringBuilder = new StringBuilder();
Path propertyPath = constraintViolation.getPropertyPath();
if (propertyPath != null) {
Iterator<Path.Node> pathIterator = constraintViolation.getPropertyPath().iterator();
String node = null;
while (pathIterator.hasNext())
node = pathIterator.next().getName();
if (node != null)
stringBuilder.append(node).append(" ");
}
return stringBuilder.append(constraintViolation.getMessage()).toString();
}).collect(Collectors.toList());
responseBuilder.entity(new ValidationErrorMessage(ImmutableList.copyOf(messages)));
} else if (message != null) {
responseBuilder.entity(new ValidationErrorMessage(ImmutableList.of(message)));
}
return responseBuilder.build();
}
}

public static class UnauthorizedExceptionMapper implements ExceptionMapper<UnauthorizedException> {
@Override
public Response toResponse(UnauthorizedException exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package bisq.api.http.exceptions;

public class ExperimentalFeatureException extends RuntimeException {
public ExperimentalFeatureException() {
super("Experimental features disabled");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package bisq.api.http.exceptions;

public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
54 changes: 54 additions & 0 deletions api/src/main/java/bisq/api/http/facade/PaymentAccountFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package bisq.api.http.facade;

import bisq.api.http.exceptions.NotFoundException;
import bisq.api.http.model.PaymentAccountList;
import bisq.api.http.model.payment.PaymentAccountHelper;

import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountManager;
import bisq.core.user.User;

import javax.inject.Inject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class PaymentAccountFacade {

private final PaymentAccountManager paymentAccountManager;
private final User user;

@Inject
public PaymentAccountFacade(PaymentAccountManager paymentAccountManager, User user) {
this.paymentAccountManager = paymentAccountManager;
this.user = user;
}

public PaymentAccount addPaymentAccount(PaymentAccount paymentAccount) {
return paymentAccountManager.addPaymentAccount(paymentAccount);
}

public void removePaymentAccount(String id) {
PaymentAccount paymentAccount = user.getPaymentAccount(id);
if (paymentAccount == null) {
throw new NotFoundException("Payment account not found: " + id);
}
user.removePaymentAccount(paymentAccount);
}

public PaymentAccountList getAccountList() {
PaymentAccountList paymentAccountList = new PaymentAccountList();
paymentAccountList.paymentAccounts = getPaymentAccountList().stream()
.map(PaymentAccountHelper::toRestModel)
.collect(Collectors.toList());
return paymentAccountList;
}

private List<PaymentAccount> getPaymentAccountList() {
Set<PaymentAccount> paymentAccounts = user.getPaymentAccounts();
return null == paymentAccounts ? Collections.emptyList() : new ArrayList<>(paymentAccounts);
}
}
11 changes: 11 additions & 0 deletions api/src/main/java/bisq/api/http/model/PaymentAccountList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package bisq.api.http.model;

import bisq.api.http.model.payment.PaymentAccount;

import java.util.List;

public class PaymentAccountList {

public List<PaymentAccount> paymentAccounts;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package bisq.api.http.model.payment;

import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.FiatCurrency;
import bisq.core.locale.TradeCurrency;
import bisq.core.payment.payload.PaymentAccountPayload;

import java.util.List;
import java.util.Optional;



import javax.validation.ValidationException;

public abstract class AbstractPaymentAccountConverter<B extends bisq.core.payment.PaymentAccount, BP extends PaymentAccountPayload, R extends PaymentAccount> implements PaymentAccountConverter<B, BP, R> {

protected void toBusinessModel(B business, R rest) {
if (rest.accountName != null)
business.setAccountName(rest.accountName);
business.getTradeCurrencies().clear();
CurrencyConverter currencyConverter;
if (rest instanceof CryptoCurrencyPaymentAccount)
currencyConverter = new CryptoCurrencyConverter();
else
currencyConverter = new FiatCurrencyConverter();

if (rest.selectedTradeCurrency != null)
business.setSelectedTradeCurrency(currencyConverter.convert(rest.selectedTradeCurrency));
if (rest.tradeCurrencies != null)
rest.tradeCurrencies.forEach(currencyCode -> business.addCurrency(currencyConverter.convert(currencyCode)));
}

protected void toRestModel(R rest, B business) {
rest.id = business.getId();
rest.accountName = business.getAccountName();
TradeCurrency selectedTradeCurrency = business.getSelectedTradeCurrency();
if (selectedTradeCurrency != null)
rest.selectedTradeCurrency = selectedTradeCurrency.getCode();
List<TradeCurrency> tradeCurrencies = business.getTradeCurrencies();
if (tradeCurrencies != null)
tradeCurrencies.forEach(currency -> rest.tradeCurrencies.add(currency.getCode()));
}

protected void toRestModel(R rest, BP business) {
rest.paymentDetails = business.getPaymentDetails();
}

private interface CurrencyConverter {
TradeCurrency convert(String currencyCode);
}

private static class FiatCurrencyConverter implements CurrencyConverter {
@Override
public TradeCurrency convert(String currencyCode) {
return new FiatCurrency(currencyCode);
}
}

private static class CryptoCurrencyConverter implements CurrencyConverter {
@Override
public TradeCurrency convert(String currencyCode) {
Optional<CryptoCurrency> cryptoCurrencyOptional = CurrencyUtil.getCryptoCurrency(currencyCode);
if (!cryptoCurrencyOptional.isPresent()) {
throw new ValidationException("Unsupported crypto currency code: " + currencyCode);
}
return cryptoCurrencyOptional.get();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package bisq.api.http.model.payment;

import bisq.core.payment.payload.PaymentMethod;

import com.fasterxml.jackson.annotation.JsonTypeName;



import org.hibernate.validator.constraints.NotBlank;

@JsonTypeName(PaymentMethod.ADVANCED_CASH_ID)
public class AdvancedCashPaymentAccount extends PaymentAccount {

@NotBlank
public String accountNr;

public AdvancedCashPaymentAccount() {
super(PaymentMethod.ADVANCED_CASH_ID);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package bisq.api.http.model.payment;

import bisq.core.payment.AdvancedCashAccount;
import bisq.core.payment.payload.AdvancedCashAccountPayload;

public class AdvancedCashPaymentAccountConverter extends AbstractPaymentAccountConverter<AdvancedCashAccount, AdvancedCashAccountPayload, AdvancedCashPaymentAccount> {

@Override
public AdvancedCashAccount toBusinessModel(AdvancedCashPaymentAccount rest) {
AdvancedCashAccount business = new AdvancedCashAccount();
business.init();
business.setAccountNr(rest.accountNr);
toBusinessModel(business, rest);
return business;
}

@Override
public AdvancedCashPaymentAccount toRestModel(AdvancedCashAccount business) {
AdvancedCashPaymentAccount rest = toRestModel((AdvancedCashAccountPayload) business.getPaymentAccountPayload());
toRestModel(rest, business);
return rest;
}

@Override
public AdvancedCashPaymentAccount toRestModel(AdvancedCashAccountPayload business) {
AdvancedCashPaymentAccount rest = new AdvancedCashPaymentAccount();
rest.accountNr = business.getAccountNr();
toRestModel(rest, business);
return rest;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package bisq.api.http.model.payment;

import bisq.core.payment.payload.PaymentMethod;

import com.fasterxml.jackson.annotation.JsonTypeName;



import org.hibernate.validator.constraints.NotBlank;

@JsonTypeName(PaymentMethod.ALI_PAY_ID)
public class AliPayPaymentAccount extends PaymentAccount {

@NotBlank
public String accountNr;

public AliPayPaymentAccount() {
super(PaymentMethod.ALI_PAY_ID);
}
}
Loading

0 comments on commit c7fa660

Please sign in to comment.