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