diff --git a/README.md b/README.md index 9f612e5..ce0e8ab 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,50 @@ # qpay-peak-flutter -## Switch from Test to Live Environment +## Environment Switching (Test vs Live) -This project currently reads runtime API settings from `assets/.env` and loads Mastercard Checkout for web from `web/index.html`. +Environment selection is command-driven with `--dart-define=APP_ENV=...`. -Use the checklist below before building for production. +- `APP_ENV=test` loads `assets/.env.test` +- `APP_ENV=live` loads `assets/.env.live` +- If `APP_ENV` is not provided, default is `test` -### 1) Update API and simulation flags (`assets/.env`) +For web, Mastercard Checkout is loaded dynamically from `GATEWAY_SCRIPT_URL` in the active env file, so `web/index.html` no longer needs manual test/live script edits. -In `assets/.env`, make sure the active (uncommented) values are set for live: +### Env files -```dotenv -BASE_URL=https://peakapi.qantra.co.zw/api -SIMULATE_PAYMENT_SUCCESS=false +Set environment-specific values in: + +- `assets/.env.test` +- `assets/.env.live` + +Supported keys: + +- `BASE_URL` +- `CLIENTID` +- `SIMULATE_PAYMENT_SUCCESS` +- `GATEWAY_SCRIPT_URL` + +### Run in Test + +```bash +flutter run --dart-define=APP_ENV=test ``` -Notes: -- Keep only one active `BASE_URL` line. -- `SIMULATE_PAYMENT_SUCCESS` must be `false` in live to avoid fake-success payment flows. +### Run in Live -### 2) Update Mastercard Checkout script (`web/index.html`) - -In `web/index.html`, switch the checkout script from test to live: - -```html - +```bash +flutter run --dart-define=APP_ENV=live ``` -And ensure the test script is not active: +### Build in Live -```html - +```bash +flutter build web --dart-define=APP_ENV=live +flutter build apk --dart-define=APP_ENV=live ``` -### 3) Quick pre-release verification +### Quick pre-release checks -Before release, confirm: -- `assets/.env` points to the live API URL. -- `assets/.env` has `SIMULATE_PAYMENT_SUCCESS=false`. -- `web/index.html` loads `na.gateway.mastercard.com` (not `test-gateway.mastercard.com`). - -## Switch back to Test - -To return to test: -- Restore the test `BASE_URL` in `assets/.env`. -- Set `SIMULATE_PAYMENT_SUCCESS=true` (only if needed for local testing). -- Re-enable the test checkout script in `web/index.html`. - -## Source of Truth - -If environment settings change in future, update this README section together with `assets/.env` and `web/index.html` so release steps stay accurate. +- Confirm `assets/.env.live` points to the live API (`BASE_URL`). +- Confirm `assets/.env.live` has `SIMULATE_PAYMENT_SUCCESS=false`. +- Confirm `assets/.env.live` uses live gateway script URL in `GATEWAY_SCRIPT_URL`. diff --git a/assets/.env.live b/assets/.env.live new file mode 100644 index 0000000..61bcc39 --- /dev/null +++ b/assets/.env.live @@ -0,0 +1,5 @@ +BASE_URL=https://peakapi.qantra.co.zw/api +CLIENTID=77433712483-ng7pntvcpf6tnjccriuqm8dbna8vvp3b.apps.googleusercontent.com +SIMULATE_PAYMENT_SUCCESS=false +GATEWAY_SCRIPT_URL=https://na.gateway.mastercard.com/static/checkout/checkout.min.js + diff --git a/assets/.env.test b/assets/.env.test new file mode 100644 index 0000000..2d5fdc1 --- /dev/null +++ b/assets/.env.test @@ -0,0 +1,5 @@ +BASE_URL=http://192.168.100.85:6950/api +CLIENTID=77433712483-ng7pntvcpf6tnjccriuqm8dbna8vvp3b.apps.googleusercontent.com +SIMULATE_PAYMENT_SUCCESS=true +GATEWAY_SCRIPT_URL=https://test-gateway.mastercard.com/static/checkout/checkout.min.js + diff --git a/lib/interop.dart b/lib/interop.dart index 7dd7770..a671e85 100644 --- a/lib/interop.dart +++ b/lib/interop.dart @@ -1,7 +1,48 @@ +import 'dart:async'; import 'dart:js_interop'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:web/web.dart' as web; + +const String _defaultGatewayScriptUrl = + 'https://test-gateway.mastercard.com/static/checkout/checkout.min.js'; + +@JS('Checkout') +external JSAny? _checkout; + @JS('configure') external void configure(String sessionID); @JS('showEmbeddedPage') external void showEmbeddedPage(); + +Future ensureCheckoutScriptLoaded() async { + final existing = web.document.querySelector('#mastercard-checkout-script'); + if (existing is web.HTMLScriptElement) { + await _waitForCheckout(); + return; + } + + final scriptUrl = + dotenv.env['GATEWAY_SCRIPT_URL'] ?? _defaultGatewayScriptUrl; + + final scriptElement = web.HTMLScriptElement() + ..id = 'mastercard-checkout-script' + ..src = scriptUrl + ..async = true; + + web.document.head?.append(scriptElement); + await _waitForCheckout(); +} + +Future _waitForCheckout() async { + const maxAttempts = 100; + for (var attempt = 0; attempt < maxAttempts; attempt++) { + if (_checkout != null) { + return; + } + await Future.delayed(const Duration(milliseconds: 100)); + } + + throw StateError('Mastercard Checkout script failed to load before timeout.'); +} diff --git a/lib/main.dart b/lib/main.dart index f70745b..2688981 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,21 +29,34 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; -const String testEnv = "assets/.env"; -const String liveEnv = "assets/.env"; +const String testEnv = 'assets/.env.test'; +const String liveEnv = 'assets/.env.live'; +const String appEnv = String.fromEnvironment('APP_ENV', defaultValue: 'test'); + +String resolveEnvFile(String env) { + switch (env.toLowerCase()) { + case 'live': + return liveEnv; + case 'test': + default: + return testEnv; + } +} void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await dotenv.load(fileName: testEnv); + await dotenv.load(fileName: resolveEnvFile(appEnv)); runApp( MultiProvider( providers: [ - ChangeNotifierProvider(create: (_) => TransactionController()), - ChangeNotifierProvider(create: (_) => OnboardingController()), + ChangeNotifierProvider( + create: (_) => TransactionController(), + ), + ChangeNotifierProvider( + create: (_) => OnboardingController(), + ), ], child: const MyApp(), ), diff --git a/lib/screens/gateway/gateway_web_screen.dart b/lib/screens/gateway/gateway_web_screen.dart index 8457be9..4a1c668 100644 --- a/lib/screens/gateway/gateway_web_screen.dart +++ b/lib/screens/gateway/gateway_web_screen.dart @@ -60,6 +60,8 @@ class _GatewayWebScreenState extends State { } void initIframeView() async { + await ensureCheckoutScriptLoaded(); + 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..."]. @@ -187,10 +189,8 @@ class _GatewayWebScreenState extends State { horizontal: 20, ), child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Once done on this page, check transaction status', @@ -211,9 +211,7 @@ class _GatewayWebScreenState extends State { .colorScheme .primary .withValues(alpha: 0.15), - borderRadius: BorderRadius.circular( - 12, - ), + borderRadius: BorderRadius.circular(12), ), child: Text( 'Check status', @@ -224,7 +222,7 @@ class _GatewayWebScreenState extends State { ], ), ), - SizedBox(height: 10,), + SizedBox(height: 10), SizedBox( width: double.infinity, height: constraints.maxHeight + 300, diff --git a/web/index.html b/web/index.html index 9ce5a39..8d1fbc8 100644 --- a/web/index.html +++ b/web/index.html @@ -33,11 +33,12 @@ Velocity - -