0

I want to build a payment page with the use of a payment page provider. I therefore created a class which builds with a futureBuilder the needed Widgets. From an API I want to receive an URL which I want to embed with an iFrame. However, the future data is every time null and the snapshot has an error. But unfortunately this error is empty. From FlutterDevTools Network I could see that the http request remains pending. In Python I tried to do this request and it worked there (at the end I posted the relevant code of my script)

I struggle with two things. 1) I didn't find information how to solve the problem when the error remains empty and 2) why does the http request keep pending?

I tried e.g.

try {
      response = await http.post(uriUrl, headers: headers, body: body);
    } catch (e) {
      print(e.toString());
    }

But the error is empty. So it didn't help either.

[UPDATE 21.10.21]: I reached back to the people from the API and they see an incoming request, but my side (client) is cancelling before the server is able to respond.

enter image description here

What I've found is that 499 is a CLIENT CLOSED REQUEST which is a non-standard status code introduced by nginx for the case when a client closes the connection while nginx is processing the request.

I've found Possible reason for NGINX 499 error codes and people are suggesting increasing the timeout, but honestly I am overwhelmed as I am inexperienced with more elaborate http questions.

class _PaymentState extends State<Payment> {
  late Future<PaymentPage> paymentPage;

  @override
  void initState() {
    paymentPage =
        APIManagerSaferpay().getPaymentPage(widget.price, widget.description);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: FutureBuilder<PaymentPage>(
            future: paymentPage,
            builder: (context, snapshot) {
              if (snapshot.hasError) {
                // the code ends up here
                return Center(child: Text(snapshot.error.toString()));
              }
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                  break;
                case ConnectionState.active:
                  return const Center(child: CircularProgressIndicator());
                case ConnectionState.done:
                  //debugger();
                  String url = snapshot.data!.redirectUrl;
                  return Center(
                      child: WebView(
                    initialUrl: Uri.dataFromString(
                            '<html><body><iframe src="$url"></iframe></body></html>',
                            mimeType: 'text/html')
                        .toString(),
                    javascriptMode: JavascriptMode.unrestricted,
                  ));

This is how I wrote the async function getPaymentPage that is invoked by the initState() method.

class APIManagerSaferpay {
  final String customerId = '******';
  final String terminalId = '********';
  String url = 'https://test.saferpay.com';
  final String authHeader = 'API_******_********:JsonApiPwd1_************';


  Future<PaymentPage> getPaymentPage(double total, String description) async {
    url = 'https://test.saferpay.com/api/Payment/v1/PaymentPage/Initialize';
    var bytes = utf8.encode(authHeader);
    var base64StrCred = base64.encode(bytes);
    var page = null;

    final queryParameters = {
      "RequestHeader": {
        "SpecVersion": "1.24",
        "CustomerId": "******",
        "RequestId": "1234567890",
        "RetryIndicator": 0
      },
      "TerminalId": "********",
      "PaymentMethods": ["Twint"],
      "Payment": {
        "Amount": {"Value": "$total", "CurrencyCode": "CHF"},
        "OrderId": "1234",
        "Description": "Lunchroom"
      },
      "ReturnUrls": {
        "Success": "https://amazon.com", // atm just arbitrary pages
        "Fail": "https://youtube.com"
      }
    };
    final body = jsonEncode(queryParameters);
    final uriUrl = Uri.parse(url);
    final headers = {
      'Authorization': 'Basic $base64StrCred',
      'Content-Type': 'application/json; charset=utf-8',
      'Accept': 'application/json'
    };
    var response = null;
    response = await http.post(uriUrl, headers: headers, body: body);

    if (response.statusCode == 200) {
      // this code is never reached
      var jsonString = response.body;
      var decoded = utf8.decode(jsonString.runes.toList());
      page = PaymentPageFromJson(decoded);
    }
    if (response.statusCode >= 400) {
      // this code is never reached
    }
    return page;
  }
}

Screenshots from FlutterDevTools:

Flutter DevTools Network (1/2)

The request remains empty even though I defined the request header:

interestingly, Request header is empty

Screenshot shows that the request remains pending after the code broke (// the code ends up here)

the http request remains pending

Functioning Python Code

import base64

...
authHeader = f'{username}:{pw}'
encodedCredentials = base64.b64encode(authHeader.encode()).decode()
header = {"Authorization": f"Basic {encodedCredentials}", "Content-Type": "application/json; charset=utf-8", "accept": "application/json"}

queryParameters = {
    "RequestHeader": {
        "SpecVersion": "1.24",
        "CustomerId": ******,
        "RequestId": "ccfbac3a8ef5151ae98035f45a609741",
        "RetryIndicator": 0
    },
    "TerminalId": ********,
    "PaymentMethods": ["TWINT"],
    "Payment": {
        "Amount": {"Value": "200", "CurrencyCode": "CHF"},
        "OrderId": "2",
        "Description": 'Lunchroom:'
    },
    "ReturnUrls": {
        "Success": 'https://amazon.com',
        "Fail": 'https://youtube.com'
    }
}


response = requests.post(url="https://test.saferpay.com/api/Payment/v1/PaymentPage/Initialize", headers=header, json=queryParameters)
Yves Boutellier
  • 1,286
  • 9
  • 19
  • 1
    Maybe the server is not sending a response or crashed check from server-side if the response is not able to receive – Hemanth S Oct 17 '21 at 17:25
  • As @HemanthS said, the issue might be coming from the backend, in case you don't have access to the backend to check if it is crashing or timing out, you could try calling the API from a controlled environment such as Postman - Insomnia... And check if the error still exists. – Mohammad Kurjieh Oct 17 '21 at 18:41
  • @MohammadKurjieh what do you mean with a "controlled" environment? I tried and succeeded in communicating with the API with a python script... I also wanted to secure myself that I did no obvious mistake in my code. From your comment I assume that you didn't find any? Right? – Yves Boutellier Oct 19 '21 at 04:30
  • @YvesBoutellier there exist API testing software that we can use to guarantee that there is no mistake in the api request sent from the client to the server, examples: Postman - Insomnia... But since you are saying that it is working on python then we can remove the server fault from the equation. But please make sure that the request you are sending from python and the request sent from flutter identical. I gave your code a look and it looks fine, but it is always those silly bugs that creep in. – Mohammad Kurjieh Oct 19 '21 at 04:37
  • I really could not find any difference between the params, headers and urls in my python vs flutter script. Do you have any other idea how this might have gone wrong? In my app I have also called a different API (GET) and it worked fine. @MohammadKurjieh – Yves Boutellier Oct 19 '21 at 05:14
  • @YvesBoutellier Try to inspect the jsonEncoded body and base64encoded auth and the headers, it might be a conversion error. Or the backend is receiving a string instead of an integer, this might also break it. – Mohammad Kurjieh Oct 19 '21 at 05:31
  • I checked the body, and base64encoded auth, as well as the headers and I couldn't find any possible error. @MohammadKurjieh – Yves Boutellier Oct 19 '21 at 07:51
  • @MohammadKurjieh I found out that it's a 499. Do you know how to solve this with Flutter? – Yves Boutellier Oct 21 '21 at 07:17

0 Answers0