From f70246d8270869b31c2de8ed924a63f07c910b94 Mon Sep 17 00:00:00 2001 From: Fernando Gomes Date: Sat, 13 Dec 2025 19:29:32 -0300 Subject: [PATCH] Fixes #121 --- .../java/com/starkbank/PaymentRequest.java | 74 ++++++++++----- src/test/java/TestPaymentRequest.java | 90 +++++++++++++++++-- 2 files changed, 135 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/starkbank/PaymentRequest.java b/src/main/java/com/starkbank/PaymentRequest.java index c43340d..b0d3ddc 100644 --- a/src/main/java/com/starkbank/PaymentRequest.java +++ b/src/main/java/com/starkbank/PaymentRequest.java @@ -7,6 +7,7 @@ import com.starkbank.utils.Rest; import com.starkcore.utils.SubResource; import com.starkcore.utils.GsonEvent; +import com.google.gson.annotations.JsonAdapter; import java.lang.reflect.Type; import java.util.ArrayList; @@ -15,9 +16,10 @@ import java.util.Map; +@JsonAdapter(PaymentRequest.Deserializer.class) public final class PaymentRequest extends Resource { static ClassData data = new ClassData(PaymentRequest.class, "PaymentRequest"); - + static { GsonEvent.registerTypeAdapter(PaymentRequest.class, new PaymentRequest.Deserializer()); } @@ -171,31 +173,54 @@ public PaymentRequest(){ public static class Deserializer implements JsonDeserializer { @Override public PaymentRequest deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctw) throws JsonParseException { - JsonElement resourceElement = json.getAsJsonObject().get("payment"); - json.getAsJsonObject().remove("payment"); - PaymentRequest request = new Gson().fromJson(json, PaymentRequest.class); + Gson gson = GsonEvent.getInstance(); + JsonObject obj = json.getAsJsonObject(); + + // Build the base request manually to avoid recursive deserialization + PaymentRequest request = new PaymentRequest(); + request.id = obj.has("id") && !obj.get("id").isJsonNull() ? obj.get("id").getAsString() : null; + request.centerId = obj.has("centerId") && !obj.get("centerId").isJsonNull() ? obj.get("centerId").getAsString() : null; + request.type = obj.has("type") && !obj.get("type").isJsonNull() ? obj.get("type").getAsString() : null; + request.due = obj.has("due") && !obj.get("due").isJsonNull() ? obj.get("due").getAsString() : null; + request.amount = obj.has("amount") && !obj.get("amount").isJsonNull() ? obj.get("amount").getAsLong() : null; + request.description = obj.has("description") && !obj.get("description").isJsonNull() ? obj.get("description").getAsString() : null; + request.status = obj.has("status") && !obj.get("status").isJsonNull() ? obj.get("status").getAsString() : null; + request.updated = obj.has("updated") && !obj.get("updated").isJsonNull() ? obj.get("updated").getAsString() : null; + request.created = obj.has("created") && !obj.get("created").isJsonNull() ? obj.get("created").getAsString() : null; + + if (obj.has("tags") && obj.get("tags").isJsonArray()) { + JsonArray tagsArray = obj.getAsJsonArray("tags"); + String[] tags = new String[tagsArray.size()]; + for (int i = 0; i < tagsArray.size(); i++) { + tags[i] = tagsArray.get(i).isJsonNull() ? null : tagsArray.get(i).getAsString(); + } + request.tags = tags; + } + + // Deserialize payment according to type + JsonElement resourceElement = obj.get("payment"); Resource resource = null; switch (request.type) { case "transfer": - resource = new Gson().fromJson(resourceElement, Transfer.class); + resource = gson.fromJson(resourceElement, Transfer.class); break; case "transaction": - resource = new Gson().fromJson(resourceElement, Transaction.class); + resource = gson.fromJson(resourceElement, Transaction.class); break; case "boleto-payment": - resource = new Gson().fromJson(resourceElement, BoletoPayment.class); + resource = gson.fromJson(resourceElement, BoletoPayment.class); break; case "utility-payment": - resource = new Gson().fromJson(resourceElement, UtilityPayment.class); + resource = gson.fromJson(resourceElement, UtilityPayment.class); break; case "tax-payment": - resource = new Gson().fromJson(resourceElement, TaxPayment.class); + resource = gson.fromJson(resourceElement, TaxPayment.class); break; case "darf-payment": - resource = new Gson().fromJson(resourceElement, DarfPayment.class); + resource = gson.fromJson(resourceElement, DarfPayment.class); break; case "brcode-payment": - resource = new Gson().fromJson(resourceElement, BrcodePayment.class); + resource = gson.fromJson(resourceElement, BrcodePayment.class); break; default: break; @@ -203,15 +228,20 @@ public PaymentRequest deserialize(JsonElement json, Type typeOfT, JsonDeserializ request.payment = resource; - resourceElement = json.getAsJsonObject().get("actions"); - for (LinkedTreeMap action : (List>) new Gson().fromJson(resourceElement, List.class)){ - request.actions.add(new PaymentRequest.Action( - (String) action.get("name"), - (String) action.get("action"), - (String) action.get("type"), - (String) action.get("id") - )); + // Parse actions with safe initialization + List parsedActions = new ArrayList<>(); + JsonElement actionsElement = obj.get("actions"); + if (actionsElement != null && !actionsElement.isJsonNull()) { + for (LinkedTreeMap action : (List>) gson.fromJson(actionsElement, List.class)) { + parsedActions.add(new PaymentRequest.Action( + (String) action.get("name"), + (String) action.get("action"), + (String) action.get("type"), + (String) action.get("id") + )); + } } + request.actions = parsedActions; return request; } @@ -477,7 +507,7 @@ private static String getType(Resource payment) throws Exception{ return "brcode-payment"; throw new Exception("Payment must either be a Transfer, a Transaction, a BoletoPayment, a BrcodePayment, " + - "a UtilityPayment, a TaxPayment or a DarfPayment."); + "a UtilityPayment, a TaxPayment or a DarfPayment."); } public final static class Log extends Resource { @@ -562,9 +592,9 @@ public final static class Action extends SubResource { /** * PaymentRequest.Action object - * + * * Used to define a action in the payment request - * + * * Parameters: * @param name [string]: name of the user that took the action. ex: "Stark Project" * @param action [string]: action type. ex "requested", "approved" diff --git a/src/test/java/TestPaymentRequest.java b/src/test/java/TestPaymentRequest.java index dfd0de4..bf51870 100644 --- a/src/test/java/TestPaymentRequest.java +++ b/src/test/java/TestPaymentRequest.java @@ -1,3 +1,5 @@ +import static org.junit.Assert.*; + import com.starkbank.PaymentRequest; import com.starkbank.Settings; import com.starkbank.Transaction; @@ -10,6 +12,7 @@ import org.junit.Assert; import org.junit.AssumptionViolatedException; import org.junit.Test; +import com.google.gson.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -39,10 +42,83 @@ public void testCreate() throws Exception{ } for(PaymentRequest request : requests) { - Assert.assertNotNull(request.id); + assertNotNull(request.id); } } + @Test + public void testDeserializeCreateResponsePayload() { + String payload = "{\n" + + " \"message\" : \"Requisição(ões) de pagamento(s) criada(s) com sucesso\",\n" + + " \"requests\" : [ {\n" + + " \"actions\" : [ {\n" + + " \"action\" : \"requested\",\n" + + " \"email\" : \"\",\n" + + " \"id\" : \"12345454444\",\n" + + " \"name\" : \"Proj\",\n" + + " \"pictureUrl\" : \"\",\n" + + " \"status\" : \"active\",\n" + + " \"type\" : \"project\"\n" + + " }, {\n" + + " \"action\" : \"required\",\n" + + " \"email\" : \"fgomes@helpnei.com\",\n" + + " \"id\" : \"12345454444\",\n" + + " \"name\" : \"Fernando Gomes\",\n" + + " \"pictureUrl\" : \"\",\n" + + " \"status\" : \"active\",\n" + + " \"type\" : \"member\"\n" + + " }, {\n" + + " \"action\" : \"required\",\n" + + " \"email\" : \"vpaz@helpnei.com\",\n" + + " \"id\" : \"12345454444\",\n" + + " \"name\" : \"Vinicius Paz\",\n" + + " \"pictureUrl\" : \"\",\n" + + " \"status\" : \"active\",\n" + + " \"type\" : \"member\"\n" + + " } ],\n" + + " \"amount\" : 12334,\n" + + " \"attachments\" : [ ],\n" + + " \"centerId\" : \"12345671234567\",\n" + + " \"created\" : \"2025-11-23T12:32:36.965218+00:00\",\n" + + " \"description\" : \"fERNANDO teste manual (011.222.333-40)\",\n" + + " \"due\" : \"2025-11-23T12:32:36.952898+00:00\",\n" + + " \"id\" : \"12345454444\",\n" + + " \"payment\" : {\n" + + " \"accountNumber\" : \"*IEQ1ATFCgZwH8JFsfAdeurjYW9AR3JpFIwm4v7iTVnpYkY140znoQZt/1u6SyhCtQ7FlmKwgyVsAsVzR7EhEwdtLhSq4NDPLsj3NxQTFakw=\",\n" + + " \"accountType\" : \"payment\",\n" + + " \"amount\" : 12334,\n" + + " \"bankCode\" : \"123456\",\n" + + " \"bankName\" : \"NU PAGAMENTOS - IP\",\n" + + " \"branchCode\" : \"*7at/HwNgLRQDwejLYsmwPHRe4qlN/WToRlf/3aNi22Q=\",\n" + + " \"description\" : \"Cash-out request ID: e53a40d3-76f2-4da2-a217-b2829faa1c05\",\n" + + " \"externalId\" : \"e53a40d3-76f2-4da2-a217-b2829faa1c05\",\n" + + " \"name\" : \"fERNANDO teste manual\",\n" + + " \"tags\" : [ \"requestId:e53a40d3-76f2-4da2-a217-b2829faa1c05\" ],\n" + + " \"taxId\" : \"011.222.333-40\"\n" + + " },\n" + + " \"status\" : \"pending\",\n" + + " \"tags\" : [ \"requestid:e53a40d3-76f2-4da2-a217-b2829faa1c05\" ],\n" + + " \"type\" : \"transfer\",\n" + + " \"updated\" : \"2025-11-23T12:32:36.965225+00:00\"\n" + + " } ]\n" + + "}"; + + JsonObject obj = JsonParser.parseString(payload).getAsJsonObject(); + JsonArray arr = obj.getAsJsonArray("requests"); + JsonElement first = arr.get(0); + + // This must not throw (previously it would fail trying to instantiate abstract Resource) + PaymentRequest request = new Gson().fromJson(first, PaymentRequest.class); + + assertNotNull(request); + assertEquals("transfer", request.type); + assertNotNull(request.payment); + assertTrue(request.payment instanceof com.starkbank.Transfer); + assertEquals("pending", request.status); + assertNotNull(request.actions); + assertEquals(3, request.actions.size()); + } + @Test public void testQuery() throws Exception { Settings.user = utils.User.defaultProject(); @@ -57,7 +133,7 @@ public void testQuery() throws Exception { int i = 0; for (PaymentRequest request : requests) { i += 1; - Assert.assertNotNull(request.id); + assertNotNull(request.id); } } @@ -104,13 +180,13 @@ public void testPaymentRequestEventParse() throws Exception{ try { event = (Event.PaymentRequestEvent) Event.parse(content, valid_signature); } catch (InvalidSignatureError e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } - Assert.assertEquals(event.getClass(), Event.PaymentRequestEvent.class); - Assert.assertEquals(event.log.getClass(), PaymentRequest.Log.class); - Assert.assertEquals(event.log.request.getClass(), PaymentRequest.class); - Assert.assertEquals(event.log.request.payment.getClass(), BoletoPayment.class); + assertEquals(event.getClass(), Event.PaymentRequestEvent.class); + assertEquals(event.log.getClass(), PaymentRequest.Log.class); + assertEquals(event.log.request.getClass(), PaymentRequest.class); + assertEquals(event.log.request.payment.getClass(), BoletoPayment.class); } static PaymentRequest example() throws Exception{