import 'dart:convert'; import 'dart:io'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:initial_folder/base_service.dart'; import 'package:initial_folder/providers/order_provider.dart'; import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi.dart'; import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:initial_folder/theme.dart'; import 'package:url_launcher/url_launcher.dart'; // Import url_launcher import 'success_paid_course.dart'; class SnapPaymentPage extends StatefulWidget { final String transactionToken; final String orderId; final int grossAmount; final String courseTitle; final String courseThumbnail; final String courseInstructor; final String courseId; SnapPaymentPage({ required this.transactionToken, required this.orderId, required this.grossAmount, required this.courseTitle, required this.courseThumbnail, required this.courseInstructor, required this.courseId, }); @override _SnapPaymentPageState createState() => _SnapPaymentPageState(); } class _SnapPaymentPageState extends State { // Controller untuk WebView late WebViewController _controller; double _progress = 0; // Menyimpan progress loading WebView bool _isLoading = true; // Status loading halaman String? baseUrlmidtrans = dotenv.env['BASE_URL_MIDTRANS']; // Base URL Midtrans @override void initState() { super.initState(); // Inisialisasi WebViewController dan pengaturan event handler _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( NavigationDelegate( onProgress: _onProgress, // Handler progress loading onPageStarted: _onPageStarted, // Handler saat halaman mulai dimuat onPageFinished: _onPageFinished, // Handler saat halaman selesai dimuat onWebResourceError: _onWebResourceError, // Handler error resource onNavigationRequest: _onNavigationRequest, // Handler navigasi ), ) ..addJavaScriptChannel( 'BlobDataChannel', // Handler pesan dari JavaScript untuk download QRIS onMessageReceived: (JavaScriptMessage message) async { if (message.message.startsWith('error:')) { // Jika error saat fetch blob showDialog( context: context, builder: (context) => AlertDialog( title: Text('Download Sedang Tidak Tersedia'), content: Text( 'Silahkan screenshot Qris tersebut untuk menyimpan ke perangkat kamu.'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('OK'), ), ], ), ); // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text('Gagal mengunduh QRIS. Silakan coba lagi.')), // ); } else { // Jika berhasil, proses data blob await _handleBlobData(message.message); } }, ) // Memuat halaman pembayaran Midtrans ..loadRequest(Uri.parse( "$baseUrlmidtrans/snap/v2/vtweb/${widget.transactionToken}", )); } // Handler progress loading WebView void _onProgress(int progressValue) { setState(() { _progress = progressValue / 100; }); } // Handler saat halaman mulai dimuat void _onPageStarted(String url) { setState(() { _isLoading = true; }); } // Handler saat halaman selesai dimuat void _onPageFinished(String url) { setState(() { _isLoading = false; _progress = 0; }); } // Handler jika terjadi error pada resource WebView void _onWebResourceError(WebResourceError error) { setState(() { _isLoading = false; }); _showErrorDialog(error.description ?? 'Unknown Error'); } // Handler navigasi pada WebView NavigationDecision _onNavigationRequest(NavigationRequest request) { // Redirect ke aplikasi pembayaran jika diperlukan if (request.url.startsWith('https://gopay.co.id/') || request.url.startsWith('https://app.shopeepay.co.id/')) { print('Redirecting to GoPay/GoJek app'); _launchURL(request.url); return NavigationDecision.prevent; } // Jika link blob (download QRIS), jalankan fetch blob if (request.url.startsWith('blob:')) { print('Redirecting download qris'); _fetchBlobData(baseUrlmidtrans ?? "https://app.sandbox.midtrans.com", widget.transactionToken); return NavigationDecision.prevent; } // Redirect ke halaman riwayat transaksi jika status pending if (request.url.contains('transaction_status=pending') || request.url.contains('https://vocasia.id/')) { Navigator.of(context) .pushReplacementNamed(RiwayatTransaksiPending.routeName); return NavigationDecision.prevent; } // Jika pembayaran sukses, tambahkan order dan redirect ke halaman sukses if (request.url.contains('transaction_status=settlement') || request.url.contains('transaction_status=capture')) { var orderProvider = Provider.of(context, listen: false); orderProvider.addOrder( id: widget.courseId, title: widget.courseTitle, price: widget.grossAmount.toString(), imageUrl: widget.courseThumbnail, instructor: widget.courseInstructor, discountPrice: '', ); Navigator.of(context).pushReplacementNamed( SuccessPaidCourse.routeName, ); return NavigationDecision.prevent; } // Jika user menekan tombol kembali di halaman pembayaran if (request.url.contains('action=back')) { Navigator.of(context).pop(); return NavigationDecision.prevent; } // Default: lanjutkan navigasi return NavigationDecision.navigate; } // Menjalankan JavaScript untuk mengambil data blob QRIS dari WebView Future _fetchBlobData(String baseUrl, String transactionId) async { print('Fetching transaction URL: $transactionId'); final realUrl = baseUrl + "/snap/v1/transactions/" + transactionId + "/qr-code"; final script = ''' (async function() { try { const response = await fetch('$baseUrl') const blob = await response.blob(); const reader = new FileReader(); reader.onloadend = function() { const base64data = reader.result.split(',')[1]; BlobDataChannel.postMessage(base64data); }; reader.readAsDataURL(blob); } catch (error) { console.error('Failed to fetch blob:', error); BlobDataChannel.postMessage('error: ' + error.message); } })(); '''; _controller.runJavaScript(script); } // Menyimpan data QRIS hasil download ke file lokal Future _handleBlobData(String base64Data) async { try { final decodedBytes = base64Decode(base64Data); final directory = await getApplicationDocumentsDirectory(); final path = directory.path; final file = File('$path/qris_download.png'); await file.writeAsBytes(decodedBytes); await FileSaver.instance.saveAs( name: 'qris_download', ext: 'png', mimeType: MimeType.png, file: file, ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('QRIS berhasil diunduh')), ); } catch (e) { _showErrorDialog('Gagal mengunduh QRIS: $e'); } } // Menampilkan dialog error void _showErrorDialog(String errorMessage) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Error'), content: Text(errorMessage), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('OK'), ), ], ), ); } // Membuka aplikasi eksternal (misal: GoPay/ShopeePay) Future _launchURL(String url) async { if (await canLaunch(url)) { await launch(url); } else { _showErrorDialog('Could not open the app'); } } // Handler ketika user menekan tombol back (hardware/software) Future _onWillPop() async { return (await showDialog( context: context, builder: (context) => AlertDialog( title: Text('Pembayaran Belum Selesai'), content: Text('Apakah Anda yakin ingin keluar dari halaman ini?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text('Batal'), ), TextButton( onPressed: () { Navigator.of(context).pop(true); Navigator.of(context) .pushReplacementNamed(RiwayatTransaksi.routeName); }, child: Text('Keluar'), ), ], ), )) ?? false; } // Melakukan reload halaman pembayaran void _refreshPage() { _controller.reload(); } @override Widget build(BuildContext context) { // Widget utama halaman pembayaran return WillPopScope( onWillPop: _onWillPop, child: Scaffold( appBar: PreferredSize( preferredSize: Size.fromHeight(kToolbarHeight), child: AppBar( elevation: 5, title: Center( child: Text( 'Pembayaran', style: primaryTextStyle.copyWith( color: Colors.white, ), ), ), backgroundColor: primaryColor, iconTheme: IconThemeData(color: Colors.white), leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () async { bool shouldExit = await _onWillPop(); if (shouldExit) { Navigator.of(context) .pushReplacementNamed(RiwayatTransaksi.routeName); } }, ), actions: [ IconButton( icon: Icon(Icons.refresh), onPressed: _refreshPage, ), ], ), ), body: Stack( children: [ // Widget WebView untuk menampilkan halaman pembayaran RefreshIndicator( onRefresh: () async { _refreshPage(); }, child: WebViewWidget(controller: _controller), ), // Progress bar saat loading if (_isLoading) LinearProgressIndicator( value: _progress, color: sevenColor, backgroundColor: secondaryColor, ), // Spinner loading di tengah layar if (_isLoading) Center( child: CircularProgressIndicator(), ), ], ), ), ); } }