7

I've tried making a retrofit call to an API endpoint, but it's returning a 400 error, however my curl request is working perfectly fine. I can't seem to spot the error, could someone double check my work to see where I made a mistake?

The curl call that works:

curl --request POST https://connect.squareupsandbox.com/v2/payments \
    --header "Content-Type: application/json" \
    --header "Authorization: Bearer accesstoken112233" \
    --header "Accept: application/json" \
    --data '{
    "idempotency_key": "ab2a118d-53e2-47c6-88e2-8c48cb09bf9b",
    "amount_money": {
    "amount": 100,
    "currency": "USD"},
    "source_id": "cnon:CBASEITjGLBON1y5od2lsdxSPxQ"}'

My Retrofit call:

public interface IMakePayment {

        @Headers({
                "Accept: application/json",
                "Content-Type: application/json",
                "Authorization: Bearer accesstoken112233"
        })
        @POST(".")
        Call<Void> listRepos(@Body DataDto dataDto);
    }

DataDto class:

public class DataDto {

    private String idempotency_key;
    private String amount_money;
    private String source_id;

    public DataDto(String idempotency_key, String amount_money, String source_id) {
        this.idempotency_key = idempotency_key;
        this.amount_money = amount_money;
        this.source_id = source_id;
    }
}

And lastly making the retrofit call:

 DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", "{\"amount\": 100, \"currency\": \"USD\"}", "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");

        RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
        Call<Void> call = service.listRepos(dataDto);
        call.enqueue(new Callback<Void>() {
            @Override
            public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
                Log.d(TAG, "onResponse: " + response.toString());
            }

            @Override
            public void onFailure(@NonNull Call<Void> call, @NonNull Throwable t) {
                Log.d(TAG, "onFailure: Error: " + t);
            }
        });

Retrofit Instance:

public class RetrofitClientInstance {

    private static Retrofit retrofit;
    private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";


    public static Retrofit getRetrofitInstance() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

Edit 1: Changing to second parameter to JSON Object

JSONObject jsonObject = new JSONObject();
        try{
            jsonObject.put("amount", 100);
            jsonObject.put("currency", "USD");
        }catch (Exception e){
            Log.d(TAG, "onCreate: " + e);
        }
        DataDto dataDto = new DataDto("ab2a118d-53e2-47c6-88e2-8c48cb09bf9b", jsonObject, "cnon:CBASEITjGLBON1y5od2lsdxSPxQ");
DIRTY DAVE
  • 2,523
  • 2
  • 20
  • 83

4 Answers4

3

First of all, let's see what 400 means

The HyperText Transfer Protocol (HTTP) 400 Bad Request response status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

Now we are sure, the problem stands in our request (not server fault), most probably it is because you are trying to convert JSON in request (do not do this explicitly GSON will convert automatically)

Use interceptor to verify your outgoing network requests (Tell the result here)

you use @POST(".") which does not make sense, please understand BASE_URL is your server URL NOT MORE

The problem could be translating this post request

So a possible solution

  • Change base URL into "https://connect.squareupsandbox.com/"
  • Replace @POST(".") with @POST("v2/payments/")

PS. @NaveenNiraula mentioned right thing even though it did not help you, please follow his instruction, it is the correct way parsing data using GSON (make sure you include it and configure it correctly) converter

EDIT

I make it work (I eliminated 400 error code that is what you want as long as question title is concerned) partially which means I detect why 400 error was occurred and fixed it but unfortunately, I stuck the UNAUTHORIZED issue. The problem was relating to converting json and data type

data class DataDTO(
    val idempotency_key: String,
    val source_id: String,
    val amount_money: MoneyAmount

)

data class MoneyAmount(
    val amount: Int,
    val currency: String
)

I gist all code here you can refer

Jasurbek
  • 2,946
  • 3
  • 20
  • 37
  • I've tried changing the base url and the @POST but it's still giving a 400 :/ – DIRTY DAVE Jul 01 '20 at 00:13
  • "code": "UNAUTHORIZED", "detail": "Your request did not include an `Authorization` http header with an access token. The header value is expected to be of the format \"Bearer TOKEN\" (without quotation marks), where TOKEN is to be replaced with your access token (e.g. \"Bearer ABC123def456GHI789jkl0\") – Jasurbek Jul 01 '20 at 13:56
  • I have written code and modify things then end up with the above-mentioned error, I have test CURL, Postman, and Native Android Retrofit call all returns same error 401 – Jasurbek Jul 01 '20 at 13:58
  • Hey Thanks for trying. I already gave up trying to use their API. I left an issue on their github, but no response either. I'll just give you the bounty. – DIRTY DAVE Jul 01 '20 at 21:46
  • I posted working solution on gist where only auth process left. Please have look – Jasurbek Jul 02 '20 at 00:40
0

Most likely the passed JSON structure is not serialized in the same format.

"amount_money": {
    "amount": 100,
    "currency": "USD"},

I would at first use for private String amount_money; a real DTO having the amount and currency fields. This should give progress. I'm not 100% sure how the underscore mapping of attributes looks like, but this is the next step.

Add logging to be able to see the passed data. A quick search reveals this tutorial: https://futurestud.io/tutorials/retrofit-2-log-requests-and-responses. When seeing the transmitted data it should be easy to compare the expected and sent data.

k_o_
  • 5,143
  • 1
  • 34
  • 43
0

You need two DTO classes as below:

public class Amount_money
{
    private String amount;

    private String currency;

    public String getAmount ()
    {
        return amount;
    }

    public void setAmount (String amount)
    {
        this.amount = amount;
    }

    public String getCurrency ()
    {
        return currency;
    }

    public void setCurrency (String currency)
    {
        this.currency = currency;
    }

    @Override
    public String toString()
    {
        return "ClassPojo [amount = "+amount+", currency = "+currency+"]";
    }
}

And

public class DataDto
{
    private String idempotency_key;

    private Amount_money amount_money;

    private String source_id;

    public String getIdempotency_key ()
    {
        return idempotency_key;
    }

    public void setIdempotency_key (String idempotency_key)
    {
        this.idempotency_key = idempotency_key;
    }

    public Amount_money getAmount_money ()
    {
        return amount_money;
    }

    public void setAmount_money (Amount_money amount_money)
    {
        this.amount_money = amount_money;
    }

    public String getSource_id ()
    {
        return source_id;
    }

    public void setSource_id (String source_id)
    {
        this.source_id = source_id;
    }

    @Override
    public String toString()
    {
        return "ClassPojo [idempotency_key = "+idempotency_key+", amount_money = "+amount_money+", source_id = "+source_id+"]";
    }
}

You need to create object for each like under :

Amount_money am = new Amount_money();
am.setAmount("100");
am.setCurrency("USD");

DataDto dto = new DataDto();
dto.setIdempotency_key("your key");
dto.setsource_id("your id");
dto.setAmount_money(am);

RetrofitInterfaces.IMakePayment service = RetrofitClientInstance.getRetrofitInstance().create(RetrofitInterfaces.IMakePayment.class);
        Call<Void> call = service.listRepos(dataDto);
// yo get the point follow along
Naveen Niraula
  • 771
  • 10
  • 19
0

Please check your base url.

In your curl you have https://connect.squareupsandbox.com/v2/payments

But in the code you have

private static final String BASE_URL = "https://connect.squareupsandbox.com/v2/payments/";

There is extra / (slash) in the end. I've seen cases where it was the issue. Could be your problem :)

GV_FiQst
  • 1,487
  • 14
  • 30
  • The app doesn't even run when I remove the slash: ```java.lang.IllegalArgumentException: baseUrl must end in /: https://connect.squareupsandbox.com/v2/payments``` Retrofit doesn't allow it – DIRTY DAVE Jun 22 '20 at 17:32
  • it because the base url. try to set `https://connect.squareupsandbox.com/` as baseUrl and in your client interface `@POST("v2/payments")` – GV_FiQst Jun 23 '20 at 14:03
  • @DIRTYDAVE tagging you in case you miss my last comment – GV_FiQst Jun 23 '20 at 14:12
  • Thanks for tagging me. Unfortunately it is still returning the 400 error – DIRTY DAVE Jun 23 '20 at 18:10