Flutter调用Flutterwave转账API遇FormatException错误求助
Flutter调用Flutterwave转账API返回HTML格式响应导致FormatException错误
错误信息
I/flutter (26554): FormatException: Unexpected character (at character 1) I/flutter (26554): <html> I/flutter (26554): ^
该请求在Postman中能正常返回成功响应,但Flutter代码执行时触发上述错误。以下是相关请求内容及代码:
Postman中可用的请求Payload
{ "amount": 500, "narration": "Test Int'l bank transfers", "currency": "USD", "beneficiary_name": "Mark Cuban", "meta": [ { "AccountNumber": "09182972BH", "RoutingNumber": "0000000002993", "SwiftCode": "ABJG190", "BankName": "BANK OF AMERICA, N.A., SAN FRANCISCO, CA", "BeneficiaryName": "Mark Cuban", "BeneficiaryAddress": "San Francisco, 4 Newton", "BeneficiaryCountry": "US" } ] }
Postman返回的成功响应
{ "status": "success", "message": "Transfer Queued Successfully", "data": { "id": 396427, "account_number": "FOREIGN-ACCOUNT", "bank_code": "FOREIGN-BANK", "full_name": "Mark Cuban", "created_at": "2023-03-10T20:30:52.000Z", "currency": "USD", "amount": 500, "fee": 40, "status": "NEW", "reference": "ebce2909fc3a57a7", "meta": [ { "MerchantName": "TestLucas LLC", "SenderCountry": "NG", "AccountNumber": "FOREIGN-ACCOUNT", "RoutingNumber": "FOREIGN-BANK", "Sender": "TestLucas LLC", "Address": null, "MobileNumber": "23480000000000" } ], "narration": "Test Int'l bank transfers", "complete_message": "", "requires_approval": 0, "is_approved": 1, "bank_name": "FA-BANK" } }
Flutter核心代码
class UsTransferProvider extends ChangeNotifier { final Future<SharedPreferences> _pref = SharedPreferences.getInstance(); bool _isLoading = false; String _reqMessage = ''; bool get isLoading => _isLoading; String get reqMessage => _reqMessage; void usTransfer({ required String amount, required String narration, required String currency, required String bankName, required String accountNum, required String beneficiary_name, required String routingNumber, required String swiftCode, required String beneficiaryCountry, required String beneficiaryAddress, }) async { _isLoading = true; notifyListeners(); String url = 'https://api.flutterwave.com/v3/transfers'; String tokenUser = "FLWSECK_TEST-153740d351951b5f6d5ae8b903e0c467-X"; final body = { "amount": amount, "narration": narration, "currency": currency, "beneficiary_name": beneficiary_name, "meta": [ { "AccountNumber": accountNum, "RoutingNumber": routingNumber, "SwiftCode": swiftCode, "BankName": bankName, "BeneficiaryName": beneficiary_name, "BeneficiaryAddress": beneficiaryAddress, "BeneficiaryCountry": beneficiaryCountry, } ] }; print(body); Map<String, String> headers = { 'Content-Type': 'application/json', 'Charset': 'utf-8', 'Authorization': 'Bearer $tokenUser' }; print(tokenUser); //making http request http.Response req = await http.post(Uri.parse(url), headers: headers, body: json.encode(body)); try { if (req.statusCode == 200 || req.statusCode == 201) { print(req.body); _isLoading = false; _reqMessage = "Transfer Successful"; pushNotification( 'Money Transfer Complete', 'Successfully sent $amount'); notifyListeners(); } else { final res = json.decode(req.body); _isLoading = false; _reqMessage = ''; notifyListeners(); } } on SocketException catch (_) { _isLoading = false; _reqMessage = 'No Internet Connection'; notifyListeners(); print(_); } catch (e) { _isLoading = false; _reqMessage = 'Check your Internet connection'; notifyListeners(); print(e); } } void clear() { _reqMessage = ''; notifyListeners(); } }
调用代码
Consumer<UsTransferProvider>(builder: (context, auth, child) { WidgetsBinding.instance.addPostFrameCallback((_) { if (auth.reqMessage != '') { showMessage( message: auth.reqMessage, context: context, ); } auth.clear(); }); return customButton( tap: () { if (amountController.text.isEmpty || narrationController.text.isEmpty || accountController.text.isEmpty || routingController.text.isEmpty || beneficiaryAddressController.text.isEmpty || beneficiaryCountryController.text.isEmpty || beneficiaryNameController.text.isEmpty || swifcodeController.text.isEmpty || _mybank.toString().isEmpty) { showMessage( message: "All field is required", context: context, ); } else { auth.usTransfer( amount: amountController.text, narration: narrationController.text, currency: selectCurrency, bankName: _mybank.toString().trim(), accountNum: accountController.text, beneficiary_name: beneficiaryNameController.text, routingNumber: routingController.text, swiftCode: swifcodeController.text, beneficiaryCountry: beneficiaryCountryController.text, beneficiaryAddress: beneficiaryAddressController.text); } }, text: 'Send Money', textColor: white, color: primaryColor, status: auth.isLoading, borderColor: primaryColor); })
排查与解决步骤
打印完整响应内容定位问题
错误提示返回HTML,说明接口可能返回了错误页面(如401、403、502)。在catch块中新增打印完整响应的代码,明确错误原因:catch (e) { _isLoading = false; _reqMessage = 'Request failed'; notifyListeners(); print('Full response: ${req.body}'); print(e); }修正参数类型差异
Postman中amount是数字类型,但Flutter代码中传入的是字符串。将字符串转为数字后再入参:final body = { "amount": double.parse(amount), // 或int.parse(amount),根据API要求调整 // 其他参数不变 };对齐请求头与Postman配置
移除多余的Charset字段,保持请求头简洁,避免服务器拦截:Map<String, String> headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer $tokenUser' };检查网络环境差异
移动端可能存在代理、VPN或运营商网络限制,导致请求被重定向到HTML页面。可切换网络(如WiFi换4G)测试,或直接在手机浏览器访问API域名确认连通性。增加非JSON响应的容错处理
先判断响应是否为JSON格式再解析,避免直接调用json.decode抛出异常:try { final res = json.decode(req.body); if (req.statusCode == 200 || req.statusCode == 201) { print(req.body); _isLoading = false; _reqMessage = "Transfer Successful"; pushNotification('Money Transfer Complete', 'Successfully sent $amount'); } else { _reqMessage = res['message'] ?? 'Transfer failed'; } notifyListeners(); } catch (e) { _isLoading = false; _reqMessage = 'Request error: ${req.statusCode}'; notifyListeners(); print('Non-JSON response: ${req.body}'); print(e); }
内容的提问来源于stack exchange,提问作者Pamela
相关产品推荐
相关产品推荐

