254 lines
8.6 KiB
Dart
254 lines
8.6 KiB
Dart
import 'dart:js_interop';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:qpay/interop.dart';
|
|
import 'package:qpay/models/responsive_policy.dart';
|
|
import 'package:qpay/screens/gateway/gateway_controller.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
import 'package:web/web.dart' as web;
|
|
|
|
class GatewayWebScreen extends StatefulWidget {
|
|
const GatewayWebScreen({super.key});
|
|
|
|
@override
|
|
State<GatewayWebScreen> createState() => _GatewayWebScreenState();
|
|
}
|
|
|
|
class _GatewayWebScreenState extends State<GatewayWebScreen> {
|
|
late WebViewController _webViewController;
|
|
late GatewayController controller;
|
|
late String url;
|
|
String? sessionID;
|
|
|
|
bool integrationStarted = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller = GatewayController(context);
|
|
|
|
url = controller.transactionController.model.receiptData?['targetUrl'];
|
|
}
|
|
|
|
// A function to set up the ResizeObserver
|
|
void setupAttachmentObserver(web.Element element) {
|
|
final observer = web.ResizeObserver(
|
|
(JSArray entries, JSAny observer) {
|
|
// The element has been laid out (and thus attached to the DOM)
|
|
onElementAttached(element);
|
|
|
|
// Disconnect the observer once the action is done
|
|
(observer as web.ResizeObserver).disconnect();
|
|
}.toJS,
|
|
);
|
|
|
|
observer.observe(element);
|
|
}
|
|
|
|
// Called after `element` is attached to the DOM.
|
|
void onElementAttached(web.Element element) {
|
|
// Your code to execute after the element is in the DOM goes here.
|
|
print('Element with ID ${element.id} is attached to the DOM.');
|
|
// You can now safely query the DOM or call JavaScript functions that rely
|
|
// on the element being present.
|
|
|
|
Future.delayed(const Duration(milliseconds: 50), () {
|
|
initIframeView();
|
|
});
|
|
}
|
|
|
|
void initIframeView() async {
|
|
final uri = Uri.parse(url);
|
|
// 2. The pathSegments property returns a list of the parts of the path.
|
|
// For "checkout/pay/SESSION...", the segments are ["checkout", "pay", "SESSION..."].
|
|
final pathSegments = uri.pathSegments;
|
|
// 3. Check if there are any segments and return the last one,
|
|
// which is the session ID in this URL structure.
|
|
if (pathSegments.isNotEmpty) {
|
|
sessionID = pathSegments.last;
|
|
|
|
configure(sessionID!);
|
|
}
|
|
showEmbeddedPage();
|
|
|
|
Future.delayed(const Duration(seconds: 10), () async {
|
|
web.HTMLIFrameElement element = fetchIframeFromDom(
|
|
"hc-comms-layer-iframe",
|
|
)!;
|
|
print(element);
|
|
element.onload = (JSAny event) {
|
|
// This is called every time a new page loads in the iframe
|
|
print(event);
|
|
|
|
try {
|
|
final newLocation = element.contentWindow?.location.href;
|
|
print('Iframe navigated to: $newLocation');
|
|
// Perform actions based on the new URL
|
|
|
|
_navigateWhenDone(newLocation!);
|
|
} catch (e) {
|
|
// Handle potential (though unlikely for same-origin) security errors
|
|
print('Could not access iframe location: $e');
|
|
}
|
|
}.toJS;
|
|
|
|
bool simulate = bool.parse(dotenv.env['SIMULATE_PAYMENT_SUCCESS']!);
|
|
if (simulate) {
|
|
await Future.delayed(Duration(milliseconds: 100));
|
|
_navigateWhenDone("/checkout/receipt/");
|
|
}
|
|
});
|
|
}
|
|
|
|
web.HTMLIFrameElement? fetchIframeFromDom(String id) {
|
|
final element = web.window.document.querySelector('#$id');
|
|
if (element is web.HTMLIFrameElement) {
|
|
print('Found iframe in DOM: ${element.id}');
|
|
print(element.toString());
|
|
return element;
|
|
}
|
|
|
|
print('Could not find iframe with id: $id in the DOM.');
|
|
return null;
|
|
}
|
|
|
|
void _navigateWhenDone(String url) {
|
|
if (url.contains("/checkout/receipt/")) {
|
|
if (integrationStarted) {
|
|
return;
|
|
}
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
"Payment was successful, please wait while we process your order.",
|
|
),
|
|
behavior: SnackBarBehavior.floating,
|
|
backgroundColor: Colors.black87,
|
|
duration: const Duration(seconds: 10),
|
|
),
|
|
);
|
|
|
|
setState(() {
|
|
integrationStarted = true;
|
|
});
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.go('/poll');
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Complete your payment'),
|
|
leading: context.canPop()
|
|
? IconButton(
|
|
onPressed: () {
|
|
context.pop();
|
|
},
|
|
icon: const Icon(Icons.arrow_back),
|
|
)
|
|
: SizedBox.shrink(),
|
|
),
|
|
body: ListenableBuilder(
|
|
listenable: controller,
|
|
builder: (context, child) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
return SingleChildScrollView(
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: ResponsivePolicy.md.toDouble(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.black87.withAlpha(20),
|
|
),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: 10,
|
|
horizontal: 20,
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.center,
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Once done on this page, check transaction status',
|
|
style: TextStyle(fontSize: 16),
|
|
),
|
|
InkWell(
|
|
onTap: () {
|
|
context.push('/poll');
|
|
},
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary
|
|
.withValues(alpha: 0.15),
|
|
borderRadius: BorderRadius.circular(
|
|
12,
|
|
),
|
|
),
|
|
child: Text(
|
|
'Check status',
|
|
style: TextStyle(fontSize: 12),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 10,),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: constraints.maxHeight + 300,
|
|
child: HtmlElementView.fromTagName(
|
|
tagName: 'div',
|
|
onElementCreated: (element) {
|
|
final divElement =
|
|
element as web.HTMLDivElement;
|
|
divElement.id = "embed-target";
|
|
setupAttachmentObserver(element);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|