processSavedInstrumentPayment method
Process payment for the Swipe Action
Implementation
Future<void> processSavedInstrumentPayment(String instrumentRef, Map<String, dynamic> shopperDetails) async {
// 1. Prepare Headers
// Note: We generate a unique ID for the request and use the Session Token for auth
final Map<String, String> headers = {
'X-Request-Id': _generateRandomRequestId(),
'Authorization': 'Session $shopperToken',
'X-Client-Connector-Name' : 'Flutter SDK',
'Content-Type': 'application/json'
};
final deviceData = await _getDeviceDetails();
// 2. Prepare body
final Map<String, dynamic> body = {
"browserData": {
"screenHeight": MediaQuery.of(context).size.height.toInt().toString(),
"screenWidth": MediaQuery.of(context).size.width.toInt().toString(),
"acceptHeader": "application/json",
"userAgentHeader": "Flutter-SDK/1.0 (Android)", // You can fetch real UserAgent if needed
"browserLanguage": "en_US",
"ipAddress": null, // The server usually detects this automatically
"colorDepth": 24,
"javaEnabled": true,
"timeZoneOffSet": DateTime.now().timeZoneOffset.inMinutes,
"packageId": "com.boxpay.checkout.flutter" // Replace with actual package ID
},
"instrumentDetails": {
"type": "card/token",
"savedCard": {
"instrumentRef": instrumentRef // <--- The token from the Swipe UI
}
},
"shopper" : shopperDetails,
"deviceDetails": deviceData
};
try {
// 3. Make the API Call
final response = await http.post(
Uri.parse(_baseUrl),
headers: headers,
body: jsonEncode(body),
);
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
String? htmlContent;
String? redirectUrl;
String? transactionRequest;
// 1. Check for "RequiresAction" status
final statusObj = result['status'];
if (statusObj != null && statusObj['status'] == 'RequiresAction') {
// 2. Check Actions Array
if (result['actions'] != null && (result['actions'] as List).isNotEmpty) {
final action = result['actions'][0];
// Case A: Type is HTML (The JSON you provided)
if (action['type'] == 'html') {
htmlContent = action['htmlPageString'];
}
// Case B: Type is URL/Redirect (Just in case)
else if (action['type'] == 'url') {
redirectUrl = action['url'];
if (action['data'] != null) {
transactionRequest = action['data']['txnreq'].toString();
}
}
}
}
// --- NAVIGATION LOGIC ---
if (htmlContent != null || redirectUrl != null) {
// Open 3DS Page and wait for result
final navResult = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BoxPay3DSPage(
htmlContent: htmlContent, // This contains your form
redirectUrl: redirectUrl,
transactionRequest: transactionRequest,
),
),
);
// Handle Polling Result
if (navResult == "completed") {
final statusData = await fetchTransactionStatus();
final finalStatus = statusData['status'];
final transactionId = statusData['transactionId']?.toString() ?? "";
if (finalStatus == 'PAID' || finalStatus == 'SUCCESS' || finalStatus == 'APPROVED') {
onPaymentResult(PaymentResultObject("Success", transactionId));
} else {
throw Exception("Payment Status: $finalStatus");
}
} else {
// User pressed 'X' or Back
throw Exception("Payment Cancelled by user");
}
} else {
onPaymentResult(PaymentResultObject(statusObj['status'], result['transactionId']));
}
} else {
// 6. Handle API Errors
_showPaymentFailedDialog();
throw Exception("Payment failed with status: ${response.statusCode}");
}
} catch (e) {
_showPaymentFailedDialog();
throw e;
}
}