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
-
-