improved environment management

This commit is contained in:
2026-03-27 19:46:45 +02:00
parent d010ec75eb
commit 15f8e0dfd0
7 changed files with 120 additions and 55 deletions

View File

@@ -1,52 +1,50 @@
# qpay-peak-flutter # 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 Set environment-specific values in:
BASE_URL=https://peakapi.qantra.co.zw/api
SIMULATE_PAYMENT_SUCCESS=false - `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: ### Run in Live
- Keep only one active `BASE_URL` line.
- `SIMULATE_PAYMENT_SUCCESS` must be `false` in live to avoid fake-success payment flows.
### 2) Update Mastercard Checkout script (`web/index.html`) ```bash
flutter run --dart-define=APP_ENV=live
In `web/index.html`, switch the checkout script from test to live:
```html
<script src="https://na.gateway.mastercard.com/static/checkout/checkout.min.js"></script>
``` ```
And ensure the test script is not active: ### Build in Live
```html ```bash
<!-- <script src="https://test-gateway.mastercard.com/static/checkout/checkout.min.js"></script> --> 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: - Confirm `assets/.env.live` points to the live API (`BASE_URL`).
- `assets/.env` points to the live API URL. - Confirm `assets/.env.live` has `SIMULATE_PAYMENT_SUCCESS=false`.
- `assets/.env` has `SIMULATE_PAYMENT_SUCCESS=false`. - Confirm `assets/.env.live` uses live gateway script URL in `GATEWAY_SCRIPT_URL`.
- `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.

5
assets/.env.live Normal file
View File

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

5
assets/.env.test Normal file
View File

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

View File

@@ -1,7 +1,48 @@
import 'dart:async';
import 'dart:js_interop'; 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') @JS('configure')
external void configure(String sessionID); external void configure(String sessionID);
@JS('showEmbeddedPage') @JS('showEmbeddedPage')
external void showEmbeddedPage(); external void showEmbeddedPage();
Future<void> 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<void> _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.');
}

View File

@@ -29,21 +29,34 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
const String testEnv = "assets/.env"; const String testEnv = 'assets/.env.test';
const String liveEnv = "assets/.env"; 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 { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp( await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
options: DefaultFirebaseOptions.currentPlatform,
);
await dotenv.load(fileName: testEnv); await dotenv.load(fileName: resolveEnvFile(appEnv));
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider<TransactionController>(create: (_) => TransactionController()), ChangeNotifierProvider<TransactionController>(
ChangeNotifierProvider<OnboardingController>(create: (_) => OnboardingController()), create: (_) => TransactionController(),
),
ChangeNotifierProvider<OnboardingController>(
create: (_) => OnboardingController(),
),
], ],
child: const MyApp(), child: const MyApp(),
), ),

View File

@@ -60,6 +60,8 @@ class _GatewayWebScreenState extends State<GatewayWebScreen> {
} }
void initIframeView() async { void initIframeView() async {
await ensureCheckoutScriptLoaded();
final uri = Uri.parse(url); final uri = Uri.parse(url);
// 2. The pathSegments property returns a list of the parts of the path. // 2. The pathSegments property returns a list of the parts of the path.
// For "checkout/pay/SESSION...", the segments are ["checkout", "pay", "SESSION..."]. // For "checkout/pay/SESSION...", the segments are ["checkout", "pay", "SESSION..."].
@@ -187,10 +189,8 @@ class _GatewayWebScreenState extends State<GatewayWebScreen> {
horizontal: 20, horizontal: 20,
), ),
child: Row( child: Row(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.center,
CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Once done on this page, check transaction status', 'Once done on this page, check transaction status',
@@ -211,9 +211,7 @@ class _GatewayWebScreenState extends State<GatewayWebScreen> {
.colorScheme .colorScheme
.primary .primary
.withValues(alpha: 0.15), .withValues(alpha: 0.15),
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(12),
12,
),
), ),
child: Text( child: Text(
'Check status', 'Check status',
@@ -224,7 +222,7 @@ class _GatewayWebScreenState extends State<GatewayWebScreen> {
], ],
), ),
), ),
SizedBox(height: 10,), SizedBox(height: 10),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: constraints.maxHeight + 300, height: constraints.maxHeight + 300,

View File

@@ -33,11 +33,12 @@
<title>Velocity</title> <title>Velocity</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script src="https://na.gateway.mastercard.com/static/checkout/checkout.min.js">
</script>
<!-- <script src="https://test-gateway.mastercard.com/static/checkout/checkout.min.js"></script> -->
<script type="text/javascript"> <script type="text/javascript">
function configure(sessionID) { function configure(sessionID) {
if (typeof Checkout === 'undefined') {
throw new Error('Checkout SDK not loaded.');
}
Checkout.configure({ Checkout.configure({
session: { session: {
id: sessionID id: sessionID
@@ -46,6 +47,10 @@
} }
function showEmbeddedPage() { function showEmbeddedPage() {
if (typeof Checkout === 'undefined') {
throw new Error('Checkout SDK not loaded.');
}
Checkout.showEmbeddedPage('#embed-target'); Checkout.showEmbeddedPage('#embed-target');
} }
</script> </script>