diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c402c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 278ebdd..8027441 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,88 @@ # iugu_rsa_java -Lib em Java para RSA + +Client de exemplo em Java, utilizando Feign (https://github.com/OpenFeign/feign), para consumo das APIs da iugu. + +## Métodos suportados + +Esta é uma versão demonstrativa e tem suporte apenas para alguns métodos que utilizam a nova autenticação com chave RSA, +que são: + +- /v1/signature/validate +- /v1/accounts/{accountId}/request_withdraw +- /v1/transfers + +## Como utilizar + +### Criação das chaves pública e privada + +Para criação e configuração das chaves público e privada, siga as recomendações dos +links: + +- https://dev.iugu.com/reference/autentica%C3%A7%C3%A3o +- https://www.youtube.com/watch?v=oY9-8cVQzvo + +### Utilizando a chave privada no seu projeto + +Existem várias abordagens para utilização de variáveis no ambiente num projeto Java. Como sugestão, podemos fazer +o encode, em Base64, do conteúdo do arquivo da chave privada e utilizar um "gerenciador de segredos", como +o Secrets Manager da AWS ou GCP, ou qualquer outro gerenciador para armazená-lo. + +Essa abordagem facilitará a troca do segredo caso necessária e facilitará sua utilização, tornando desnecessária +a manipulação de arquivos em tempo de execução. + +Supondo que o valor está definido como uma variável de ambiente, podemos criar um método para recuperá-lo da seguinte +forma: + +```java +public String getPrivateKey() { + final String privateKeyInBase64 = System.getenv("IUGU_PRIVATE_KEY"); + return new String(Base64.getDecoder().decode(privateKeyInBase64)); +} +``` + +Neste caso, o nome da variável de ambiente utilizada foi "IUGU_PRIVATE_KEY", mas, isso vai depender de como a variável +foi definida anteriormente. + +### Utilizando o client Iugu + +Com o valor da chave privada em mãos, agora podemos utilizar a implementação do client Iugu. Para tal, precisamos +instanciar o client: + +```java +final IuguClient iuguClient = IuguClient.getProductionInstance(); +``` + +Apenas para exemplo, vamos criar uma requisição de transferência de R$10 para a conta "12345": + +```java +final TransferRequest transferRequest = new TransferRequest(); +transferRequest.setReceiverId("12345"); +transferRequest.setAmountCents(BigInteger.valueOf(10)); +``` + +Como o objetivo aqui é testar a chave, vamos chamar o método "/v1/signature/validate" utilizando essa request: + +```java +final Object response = iuguClient + .withApiToken(apiToken) + .withPrivateKey(privateKey) + .validateSignature(transferRequest); +``` + +Perceba que aqui estamos utilizando 2 métodos de segurança: + +- withApiToken: esse método inclui o "apiToken" no header da requisição; +- withPrivateKey: esse método inclui a assinatura, utilizando a chave RSA, no header da requisição. + +Se tudo foi configurado corretamente, o método "/v1/signature/validate" vai retornar um 200. + +### Exemplo completo + +A classe "IuguClientTest" possui um exemplo funcional, sendo apenas necessário definir o valor do "apiToken" e do " +privateKey". + +## Disclaimer + +Criei esse projeto de exemplo apenas para facilitar a utilização da chave RSA num ambiente real, sem necessidade da +manipulação de arquivos e tentando abstrair o máximo possível da complexidade do fluxo, trazendo complexidade apenas +para o client. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..10d48b1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + com.iugu + iugu_rsa_java + 0.0.1 + + + 17 + ${java.version} + ${java.version} + UTF-8 + + + + + io.github.openfeign + feign-core + 13.2.1 + + + io.github.openfeign + feign-jackson + 13.2.1 + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + + + \ No newline at end of file diff --git a/src/main/java/com/iugu/Iugu.java b/src/main/java/com/iugu/Iugu.java new file mode 100644 index 0000000..dc4bfd5 --- /dev/null +++ b/src/main/java/com/iugu/Iugu.java @@ -0,0 +1,28 @@ +package com.iugu; + +import com.iugu.domain.request.RequestWithdrawRequest; +import com.iugu.domain.request.TransferRequest; +import com.iugu.domain.response.RequestWithdrawResponse; +import com.iugu.domain.response.TransferResponse; +import feign.Headers; +import feign.Param; +import feign.RequestLine; + +import java.security.SignatureException; + +/** + * @author italobrunos + */ +@Headers("Content-Type: application/json") +public interface Iugu { + + @RequestLine("POST /v1/signature/validate") + Object validateSignature(Object body) throws SignatureException; + + @RequestLine("POST /v1/accounts/{accountId}/request_withdraw") + RequestWithdrawResponse requestWithdraw(RequestWithdrawRequest request, + @Param("accountId") String accountId) throws SignatureException; + + @RequestLine("POST /v1/transfers") + TransferResponse transfer(TransferRequest transferRequest) throws SignatureException; +} diff --git a/src/main/java/com/iugu/IuguClient.java b/src/main/java/com/iugu/IuguClient.java new file mode 100644 index 0000000..d8c37e2 --- /dev/null +++ b/src/main/java/com/iugu/IuguClient.java @@ -0,0 +1,210 @@ +package com.iugu; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.iugu.domain.request.RequestWithdrawRequest; +import com.iugu.domain.request.TransferRequest; +import com.iugu.domain.response.RequestWithdrawResponse; +import com.iugu.domain.response.TransferResponse; +import com.iugu.interceptor.BearerRequestInterceptor; +import com.iugu.interceptor.RsaRequestInterceptor; +import feign.*; +import feign.codec.Decoder; +import feign.codec.Encoder; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.Objects; + +/** + * @author italobrunos + */ +public class IuguClient implements Iugu { + + private final String url; + private Retryer retryer = Retryer.NEVER_RETRY; + private Logger.Level logLevel = Logger.Level.BASIC; + private String apiToken; + private RSAPrivateKey privateKey; + private final Encoder encoder; + private final Decoder decoder; + private final ObjectMapper objectMapper; + + public IuguClient(String url) { + this.url = url; + this.encoder = new JacksonEncoder(); + this.decoder = new JacksonDecoder(); + this.objectMapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .configure(SerializationFeature.INDENT_OUTPUT, true); + } + + public static IuguClient getProductionInstance() { + return new IuguClient("https://api.iugu.com"); + } + + private Iugu getClient(RequestInterceptor... requestInterceptors) { + final Feign.Builder builder = Feign.builder() + .encoder(encoder) + .decoder(decoder) + .retryer(retryer) + .logLevel(logLevel); + + final RequestInterceptor bearerRequestInterceptor = createBearerRequestInterceptor(); + if (Objects.nonNull(bearerRequestInterceptor)) { + builder.requestInterceptor(bearerRequestInterceptor); + } + + for (RequestInterceptor requestInterceptor : requestInterceptors) { + if (Objects.nonNull(requestInterceptor)) { + builder.requestInterceptor(requestInterceptor); + } + } + + return builder.target(Iugu.class, url); + } + + public IuguClient withRetryer(Retryer retryer) { + this.retryer = retryer; + return this; + } + + public IuguClient withLogLevel(Logger.Level logLevel) { + this.logLevel = logLevel; + return this; + } + + public IuguClient withApiToken(String apiToken) { + this.apiToken = apiToken; + return this; + } + + public IuguClient withPrivateKey(String privateKey) throws InvalidKeySpecException { + try { + final String privateKeyInline = privateKey + .replaceAll("-----BEGIN PRIVATE KEY-----", "") + .replaceAll("-----END PRIVATE KEY-----", "") + .replaceAll("\r", "") + .replaceAll("\n", ""); + final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyInline)); + final KeyFactory kf = KeyFactory.getInstance("RSA"); + this.privateKey = (RSAPrivateKey) kf.generatePrivate(spec); + return this; + } catch (NoSuchAlgorithmException e) { + throw new InvalidKeySpecException(e); + } + } + + private void deleteApiKey() { + this.apiToken = null; + } + + /** + * Uma vez que o requestInterceptor é criado, a apiKey é deletada para evitar uma segunda chamada errada, + * utilizando a mesma instância do client. Isso força a utilização do método @withApiKey sempre que uma + * chamada for feita. + * + * @return RequestInterceptor + */ + private RequestInterceptor createBearerRequestInterceptor() { + BearerRequestInterceptor bearerRequestInterceptor = null; + if (Objects.nonNull(this.apiToken)) { + bearerRequestInterceptor = new BearerRequestInterceptor(this.apiToken); + deleteApiKey(); + } + return bearerRequestInterceptor; + } + + private RequestInterceptor createRsaRequestInterceptor(Request.HttpMethod method, + String endpoint, + Object body) + throws SignatureException { + RsaRequestInterceptor rsaRequestInterceptor = null; + if (Objects.nonNull(apiToken) && Objects.nonNull(privateKey)) { + final String requestTimeAsString = createRequestTimeAsString(); + final String bodySigned = sign(method, endpoint, requestTimeAsString, body); + rsaRequestInterceptor = new RsaRequestInterceptor(bodySigned, requestTimeAsString); + } + return rsaRequestInterceptor; + } + + private String createRequestTimeAsString() { + return DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") + .format(ZonedDateTime.now(ZoneOffset.ofHours(-3))); + } + + private String sign(Request.HttpMethod method, + String endpoint, + String requestTime, + Object body) + throws SignatureException { + + try { + final String pattern = String.format("%s|%s\n%s|%s\n%s", + method, + endpoint, + apiToken, + requestTime, + objectMapper.writeValueAsString(body) + ); + + final Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(privateKey); + signature.update(pattern.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(signature.sign()); + + } catch (NoSuchAlgorithmException | InvalidKeyException | JsonProcessingException e) { + throw new SignatureException(e); + } + } + + @Override + public Object validateSignature(Object body) throws SignatureException { + final String endpoint = "/v1/signature/validate"; + final RequestInterceptor rsaRequestInterceptor = createRsaRequestInterceptor( + Request.HttpMethod.POST, + endpoint, + body + ); + final Iugu iugu = getClient(rsaRequestInterceptor); + return iugu.validateSignature(body); + } + + @Override + public RequestWithdrawResponse requestWithdraw(RequestWithdrawRequest request, + String accountId) throws SignatureException { + final String endpoint = String.format("/v1/accounts/%s/request_withdraw", accountId); + final RequestInterceptor rsaRequestInterceptor = createRsaRequestInterceptor( + Request.HttpMethod.POST, + endpoint, + request + ); + final Iugu iugu = getClient(rsaRequestInterceptor); + return iugu.requestWithdraw(request, accountId); + } + + @Override + public TransferResponse transfer(TransferRequest transferRequest) throws SignatureException { + final String endpoint = "/v1/transfers"; + final RequestInterceptor rsaRequestInterceptor = createRsaRequestInterceptor( + Request.HttpMethod.POST, + endpoint, + transferRequest + ); + final Iugu iugu = getClient(rsaRequestInterceptor); + return iugu.transfer(transferRequest); + } +} diff --git a/src/main/java/com/iugu/domain/Account.java b/src/main/java/com/iugu/domain/Account.java new file mode 100644 index 0000000..efc88e5 --- /dev/null +++ b/src/main/java/com/iugu/domain/Account.java @@ -0,0 +1,30 @@ +package com.iugu.domain; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +/** + * @author italobrunos + */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class Account { + + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/iugu/domain/CustomVariable.java b/src/main/java/com/iugu/domain/CustomVariable.java new file mode 100644 index 0000000..2e72120 --- /dev/null +++ b/src/main/java/com/iugu/domain/CustomVariable.java @@ -0,0 +1,38 @@ +package com.iugu.domain; + +/** + * @author italobrunos + */ +public class CustomVariable { + + private String name; + private Object value; + + public CustomVariable() { + } + + public CustomVariable(String name, Object value) { + this.name = name; + this.value = value; + } + + public static CustomVariable create(String name, Object value) { + return new CustomVariable(name, value); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } +} diff --git a/src/main/java/com/iugu/domain/deserializer/BigDecimalDeserializer.java b/src/main/java/com/iugu/domain/deserializer/BigDecimalDeserializer.java new file mode 100644 index 0000000..c290a09 --- /dev/null +++ b/src/main/java/com/iugu/domain/deserializer/BigDecimalDeserializer.java @@ -0,0 +1,43 @@ +package com.iugu.domain.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.iugu.domain.utils.BigDecimalUtils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.text.ParseException; + +/** + * @author italobrunos + */ +public class BigDecimalDeserializer extends StdDeserializer { + + public BigDecimalDeserializer() { + this(null); + } + + protected BigDecimalDeserializer(Class vc) { + super(vc); + } + + @Override + public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + final String source = jsonParser.getText(); + return parse(source); + } + + private BigDecimal parse(String source) throws IOException { + try { + return BigDecimalUtils.parseUs(source); + } catch (ParseException e) { + try { + return BigDecimalUtils.parsePtBr(source); + } catch (ParseException ex) { + throw new IOException(ex); + } + } + } +} diff --git a/src/main/java/com/iugu/domain/request/RequestWithdrawRequest.java b/src/main/java/com/iugu/domain/request/RequestWithdrawRequest.java new file mode 100644 index 0000000..4ee589e --- /dev/null +++ b/src/main/java/com/iugu/domain/request/RequestWithdrawRequest.java @@ -0,0 +1,48 @@ +package com.iugu.domain.request; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.iugu.domain.CustomVariable; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author italobrunos + */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class RequestWithdrawRequest { + + private BigDecimal amount; + private List customVariables; + + public RequestWithdrawRequest() { + this.customVariables = new ArrayList<>(); + } + + public RequestWithdrawRequest addCustomVariable(CustomVariable customVariable) { + if (Objects.isNull(this.customVariables)) { + this.customVariables = new ArrayList<>(); + } + this.customVariables.add(customVariable); + return this; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public List getCustomVariables() { + return customVariables; + } + + public void setCustomVariables(List customVariables) { + this.customVariables = customVariables; + } +} diff --git a/src/main/java/com/iugu/domain/request/TransferRequest.java b/src/main/java/com/iugu/domain/request/TransferRequest.java new file mode 100644 index 0000000..fbda8b7 --- /dev/null +++ b/src/main/java/com/iugu/domain/request/TransferRequest.java @@ -0,0 +1,57 @@ +package com.iugu.domain.request; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.iugu.domain.CustomVariable; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author italobrunos + */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class TransferRequest { + + private String receiverId; + private BigInteger amountCents; + private List customVariables; + + public TransferRequest() { + this.customVariables = new ArrayList<>(); + } + + public TransferRequest addCustomVariable(CustomVariable customVariable) { + if (Objects.isNull(this.customVariables)) { + this.customVariables = new ArrayList<>(); + } + this.customVariables.add(customVariable); + return this; + } + + public String getReceiverId() { + return receiverId; + } + + public void setReceiverId(String receiverId) { + this.receiverId = receiverId; + } + + public BigInteger getAmountCents() { + return amountCents; + } + + public void setAmountCents(BigInteger amountCents) { + this.amountCents = amountCents; + } + + public List getCustomVariables() { + return customVariables; + } + + public void setCustomVariables(List customVariables) { + this.customVariables = customVariables; + } +} diff --git a/src/main/java/com/iugu/domain/response/RequestWithdrawResponse.java b/src/main/java/com/iugu/domain/response/RequestWithdrawResponse.java new file mode 100644 index 0000000..02052c6 --- /dev/null +++ b/src/main/java/com/iugu/domain/response/RequestWithdrawResponse.java @@ -0,0 +1,36 @@ +package com.iugu.domain.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.iugu.domain.deserializer.BigDecimalDeserializer; + +import java.math.BigDecimal; + +/** + * @author italobrunos + */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class RequestWithdrawResponse { + + private String id; + + @JsonDeserialize(using = BigDecimalDeserializer.class) + private BigDecimal amount; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } +} diff --git a/src/main/java/com/iugu/domain/response/TransferResponse.java b/src/main/java/com/iugu/domain/response/TransferResponse.java new file mode 100644 index 0000000..2b6938f --- /dev/null +++ b/src/main/java/com/iugu/domain/response/TransferResponse.java @@ -0,0 +1,95 @@ +package com.iugu.domain.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.iugu.domain.Account; +import com.iugu.domain.CustomVariable; +import com.iugu.domain.deserializer.BigDecimalDeserializer; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +/** + * @author italobrunos + */ +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class TransferResponse { + + private String id; + private String createdAt; + private BigInteger amountCents; + + @JsonDeserialize(using = BigDecimalDeserializer.class) + private BigDecimal amountLocalized; + + private String updatedAt; + private Account receiver; + private Account sender; + private List customVariables; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public BigInteger getAmountCents() { + return amountCents; + } + + public void setAmountCents(BigInteger amountCents) { + this.amountCents = amountCents; + } + + public BigDecimal getAmountLocalized() { + return amountLocalized; + } + + public void setAmountLocalized(BigDecimal amountLocalized) { + this.amountLocalized = amountLocalized; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + public Account getReceiver() { + return receiver; + } + + public void setReceiver(Account receiver) { + this.receiver = receiver; + } + + public Account getSender() { + return sender; + } + + public void setSender(Account sender) { + this.sender = sender; + } + + public List getCustomVariables() { + return customVariables; + } + + public void setCustomVariables(List customVariables) { + this.customVariables = customVariables; + } +} diff --git a/src/main/java/com/iugu/domain/utils/BigDecimalUtils.java b/src/main/java/com/iugu/domain/utils/BigDecimalUtils.java new file mode 100644 index 0000000..b3fb395 --- /dev/null +++ b/src/main/java/com/iugu/domain/utils/BigDecimalUtils.java @@ -0,0 +1,37 @@ +package com.iugu.domain.utils; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.Locale; + +/** + * @author italobrunos + */ +public final class BigDecimalUtils { + + private static final Locale PT_BR = new Locale("pt", "BR"); + private static final DecimalFormat BRAZILIAN_DECIMAL_FORMAT = + new DecimalFormat("###,###.00", new DecimalFormatSymbols(PT_BR)); + + private static final DecimalFormat US_DECIMAL_FORMAT = + (DecimalFormat) DecimalFormat.getNumberInstance(Locale.US); + + static { + BRAZILIAN_DECIMAL_FORMAT.setParseBigDecimal(true); + US_DECIMAL_FORMAT.setParseBigDecimal(true); + } + + private BigDecimalUtils() { + } + + public static BigDecimal parseUs(String source) throws ParseException { + return (BigDecimal) US_DECIMAL_FORMAT.parse(source); + } + + public static BigDecimal parsePtBr(String source) throws ParseException { + final String cleanedSource = source.replaceAll("[^\\d.,-]", ""); + return (BigDecimal) BRAZILIAN_DECIMAL_FORMAT.parse(cleanedSource); + } +} diff --git a/src/main/java/com/iugu/interceptor/BearerRequestInterceptor.java b/src/main/java/com/iugu/interceptor/BearerRequestInterceptor.java new file mode 100644 index 0000000..dc1d805 --- /dev/null +++ b/src/main/java/com/iugu/interceptor/BearerRequestInterceptor.java @@ -0,0 +1,24 @@ +package com.iugu.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import java.util.Base64; + +/** + * @author italobrunos + */ +public class BearerRequestInterceptor implements RequestInterceptor { + + private final String apiToken; + + public BearerRequestInterceptor(String apiToken) { + this.apiToken = apiToken; + } + + @Override + public void apply(RequestTemplate template) { + final String apiTokenBase64 = Base64.getEncoder().encodeToString(apiToken.getBytes()); + template.header("Authorization", String.format("Bearer %s", apiTokenBase64)); + } +} diff --git a/src/main/java/com/iugu/interceptor/RsaRequestInterceptor.java b/src/main/java/com/iugu/interceptor/RsaRequestInterceptor.java new file mode 100644 index 0000000..24f63df --- /dev/null +++ b/src/main/java/com/iugu/interceptor/RsaRequestInterceptor.java @@ -0,0 +1,25 @@ +package com.iugu.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * @author italobrunos + */ +public class RsaRequestInterceptor implements RequestInterceptor { + + private final String signature; + private final String requestTime; + + public RsaRequestInterceptor(String signature, + String requestTime) { + this.signature = signature; + this.requestTime = requestTime; + } + + @Override + public void apply(RequestTemplate template) { + template.header("Signature", String.format("signature=%s", signature)); + template.header("Request-Time", requestTime); + } +} diff --git a/src/test/java/IuguClientTest.java b/src/test/java/IuguClientTest.java new file mode 100644 index 0000000..5a35320 --- /dev/null +++ b/src/test/java/IuguClientTest.java @@ -0,0 +1,31 @@ +import com.iugu.IuguClient; +import com.iugu.domain.request.TransferRequest; + +import java.math.BigInteger; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; + +/** + * @author italobrunos + */ +public class IuguClientTest { + + public static void main(String[] args) throws InvalidKeySpecException, SignatureException { + final String apiToken = ""; + final String privateKey = """ + + """; + + final TransferRequest transferRequest = new TransferRequest(); + transferRequest.setReceiverId("12345"); + transferRequest.setAmountCents(BigInteger.valueOf(10)); + + final IuguClient iuguClient = IuguClient.getProductionInstance(); + final Object response = iuguClient + .withApiToken(apiToken) + .withPrivateKey(privateKey) + .validateSignature(transferRequest); + + System.out.println(response); + } +} \ No newline at end of file