Http POST works in Postman but not in Flutter
Asked Answered
H

3

11

I am trying to do a POST request on my flutter application using the Http package. I tested my request first on the Api sandbox website, and then in Postman. It works well there, but once in Flutter, I always get a 400 Bad Request.

Here is my code in Flutter:

import 'package:http/http.dart';
import 'package:uuid/uuid.dart';
import 'package:wave_app/env/secrets.dart';
import 'package:wave_app/models/momo_token.dart';

    String url = "https://sandbox.momodeveloper.mtn.com/collection/v1_0/requesttopay";
    var uuid = Uuid();
    String requestId = uuid.v4();
    MomoToken token = await _createMomoNewTokenCollection();

    String auth = "Bearer " + token.accessToken;

    Map<String, String> headers = {
      "Authorization": auth,
      "X-Target-Environment": "sandbox",
      "X-Reference-Id": requestId,
      "Content-Type": "application/json",
      "Ocp-Apim-Subscription-Key": momoCollectionSubscriptionKey
    };

    String jsonBody = '{"amount": "5","currency": "EUR", "externalId": "123", "payer": {"partyIdType": "MSISDN","partyId": "46733123454"}, "payerMessage": "tripId-123456","payeeNote": "driverId-654321"}';

    Response response = await post(url, headers: headers, body: jsonBody);
    int statusCode = response.statusCode;

    print("STATUS CODE REQUEST TO PAY " + statusCode.toString());
    print(response.reasonPhrase.toString());
    print(response.body.toString());

    if (statusCode == 202) {
      return response.body.toString();
    } else {
      return null;
    }
  }

The api doc is here: https://momodeveloper.mtn.com/docs/services/collection/operations/requesttopay-POST?

And here is the code in curl of my Postman request (using the same variable above requestId, auth, momoCollectionSubscriptionKey)

curl --request POST \
  --url https://sandbox.momodeveloper.mtn.com/collection/v1_0/requesttopay \
  --header 'Accept: */*' \
  --header 'Accept-Encoding: gzip, deflate' \
  --header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSMjU2In0.eyJjbGllbnRJZCI6IjFmY2MzMjBhLTM0NWQtMTFlYS04NTBkLTJlNzI4Y2U4ODEyNSIsImV4cGlyZXMiOiIyMDIwLTAxLTExVDE1OjU3OjE4Ljc3NyIsInNlc3Npb25JZCI6ImZmYzc1OGE2LTM2MWEtNDM4ZS1hYjE5LWQ1ZGQ4ZmU4ZjEyOSJ9.DeoJyU6Hb0he_or1XeBxW-6s-xwdtmi0cUrYjQe0Z796bIGvvT-VJ214JaZItG-CBQpgv7dHbLfXNqr8D05Q7U9XiOtpr8mtYWQlY-MseGIHAyxp1qBuQkwjmBYBlDxQOYYfzG9SZ8tGFUI1_k59LMNYIhDlXXKa68Ym1sylZ8wfWjGuHaKVzMEH25ubiBwCLev5IHPchuF3toVP99U-HC8t95E3zrEt9dHgzn0hnwvpB31wcsu_b3vb-YZ1idHgosPc2GmKFsDruX14VniKBicCsnGHqZAkSPXwaOR6SIn4JZEEwhAIj3Oe2H5dwxloiX5rzaApdkwEg6KSoBXk8A' \
  --header 'Cache-Control: no-cache' \
  --header 'Connection: keep-alive' \
  --header 'Content-Length: 194' \
  --header 'Content-Type: application/json' \
  --header 'Host: sandbox.momodeveloper.mtn.com' \
  --header 'Ocp-Apim-Subscription-Key: 281eb****************' \
  --header 'Postman-Token: ece19062-1f0b-4873-a3ed-1bd4ada8746a,528004b2-410d-4653-9909-5197a3dc95db' \
  --header 'User-Agent: PostmanRuntime/7.20.1' \
  --header 'X-Reference-Id: 062f8aad-f529-4d0a-804c-affb888c2b8b' \
  --header 'X-Target-Environment: sandbox' \
  --header 'cache-control: no-cache' \
  --data '{\r\n  "amount": "5",\r\n  "currency": "EUR",\r\n  "externalId": "123",\r\n  "payer": {\r\n    "partyIdType": "MSISDN",\r\n    "partyId": "46733123454"\r\n  },\r\n  "payerMessage": "hi",\r\n  "payeeNote": "hi"\r\n}'

On postman and their website, I always get a 202 Accepted response. I am not sure, what I'm doing wrong here. Any help would be greatly appreciated!

------------ EDIT -------------------

I also tried with HttpClient, here is the code, but still got 400 Bad Request

HttpClient httpClient = new HttpClient();
    HttpClientRequest request = await httpClient.postUrl(Uri.parse(url));

    request.headers.set("Authorization", "Bearer " + token.accessToken);
    request.headers.set('content-type', 'application/json');
    request.headers.set("X-Target-Environment", "sandbox");
    request.headers.set("X-Reference-Id", requestId);
    request.headers.set("Ocp-Apim-Subscription-Key", momoCollectionSubscriptionKey);

    request.add(utf8.encode(jsonBody));
    HttpClientResponse response = await request.close();

    print("STATUS CODE " + response.statusCode.toString() + "   " + response.reasonPhrase);
    String reply = await response.transform(utf8.decoder).join();
    print("REPLY " + reply);
    httpClient.close();
Huntress answered 11/1, 2020 at 16:7 Comment(16)
Your Dart code seems to be correct. But we can't really test this without enrolling ourselves in the website you are trying to use.Sweeps
It might be a server problem that crops up occasionally. post will modify the content type in two ways. (1) it will lower case the header name to content-type and (2) it will change the value to application/json; charset=utf-8. Tweak your curl command to mimic these and see if it succeeds or fails. If the problem is (2) the solution is easy: change body: jsonBody to body: utf8.encode(jsonBody). The solution to (1) is trickier.Translation
@RichardHeap I just tried with this: --header 'content-type: application/json; charset=utf-8' in my curl, and I get 202 accepted in Postman. And still 400 bad Request in my flutter app. Do you have any other idea about the origin of the problem? Thanks for helping!Huntress
Try the other suggestion, just for kicks. Then assert that curl works with all lower case header names e.g. ocp-apim-subscription-keyTranslation
@RichardHeap, I tried with lowercase headers, it failed on Postman. The opc-apim-subscription-key lower case worked, but x-target-environment and x-reference-id failed with 400 bad request. Do you know how I can bypass that force lowercase in flutter?Huntress
First, try with the plain dart:io HttpClient (which is used by package:http under the hood) - it doesn't modify the header names, but you end up with a few more lines of boilerplate. Also try package:dio. I've never used it but I don't think it changes the header name case. Lastly, also complain to the API owner. Currently it violates the RFC by expecting a certain case of header name.Translation
@RichardHeap, I tried with HttpClient (see edit of post for the code) and also package:dio, but still I got 400 bad request. I also contacted the api team but no replay so far... Do you think of anything else that could pause problem? Or any way to call this api for my app? Thanks again for your help.Huntress
Created chat here: chat.stackoverflow.com/rooms/205940/momo-from-dartTranslation
@RichardHeap thanks for the tchat but i need 20 of reputation to be able to send message there, and I only have 1...Huntress
I tried adding you so that you don't need that... Strange... Let me try again.Translation
I can see the room changed to public, but I can't still add message. But I can give you the subscription key here, I will regenerate it later, if you want.Huntress
As long as you can re-generate, go ahead.Translation
Please try the chat room againTranslation
I still see "You must have 20 reputation on Stack Overflow to talk here. See the faq" and no input box on the chat roomHuntress
When I look at the info of the room, I am under user9643861 with 1 reputation. Maybe try to remove me and add me again?Huntress
See answer - maybe delete the credentials above now.Translation
T
6

This is definitely a problem with the server. The two headers X-Reference-Id and X-Target-Environment are being handled case sensitively by the server (i.e. it is not in compliance with the RFC).

It's Dart's io.HttpClient that forces headers to lower case, so this affects package:http and package:dio which both rely on it. There's a request to allow the client to preserve the case of headers but it's coming slowly as it's a breaking change.

In the meantime, try using this fork of the client which preserves header case. https://pub.dev/packages/alt_http/

Translation answered 14/1, 2020 at 16:32 Comment(2)
Ok thank you so much for your answer and your time. I will try with that package. Sorry about the chat issue, don't know why it's still not working.Huntress
At least you should be able to upvote now - ha ha :-)Translation
I
1

I solved my problem by adding this header:

'content-type': 'multipart/form-data'
Intact answered 7/6, 2023 at 4:35 Comment(0)
G
0

Solved same issue by changing jsonBody from String into Map.

 String jsonBody = '{"amount": "5","currency": "EUR", "externalId": "123", "payer": {"partyIdType": "MSISDN","partyId": "46733123454"}, "payerMessage": "tripId-123456","payeeNote": "driverId-654321"}';

Only solved when changed from String -> Map;

 Map jsonBody = {"amount": "5","currency": "EUR", "externalId": "123", "payer": {"partyIdType": "MSISDN","partyId": "46733123454"}, "payerMessage": "tripId-123456","payeeNote": "driverId-654321"};

and additionally, included the jsonEncode

Response response = await post(url, headers: headers, body: jsonEncode(jsonBody));

jsonEncode requires the import of Convert library

import 'dart:convert';
Gantlet answered 10/7, 2020 at 19:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.