moneyhash_payment 4.1.0
moneyhash_payment: ^4.1.0 copied to clipboard
MoneyHash is a Super-API infrastructure for payment orchestration and revenue operations in emerging markets.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:moneyhash_payment/data/intent_details.dart';
import 'package:moneyhash_payment/data/intent_type.dart';
import 'package:moneyhash_payment/moneyhash_payment.dart';
import 'payment/payment_screen.dart';
import 'sample_sdk.dart';
import 'scenarios_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const CheckoutScreen(),
);
}
}
/// The sample's entry point, matching iOS's `CheckoutScreen.swift` button for button: type an
/// intent, then either walk it through every state the SDK reports, hand the whole checkout to the
/// MoneyHash embed, kill the app to prove the crash flush, or open the one-off scenarios.
class CheckoutScreen extends StatefulWidget {
const CheckoutScreen({Key? key}) : super(key: key);
@override
State<CheckoutScreen> createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State<CheckoutScreen> {
// Typed here rather than compiled in, like the Android and iOS samples. Every intent call sends
// the secret, so a stale one fails them all with 401 — which is only obvious when you can see and
// edit it.
final TextEditingController _intentIdController =
TextEditingController(text: 'ZG3Xdb4');
final TextEditingController _intentSecretController =
TextEditingController(text: 'f593b4304cb094611987');
String get _intentId => _intentIdController.text.trim();
String get _intentSecret => _intentSecretController.text.trim();
/// Same instance as the payment controller and the scenarios screen: building a second one here
/// would overwrite the public key for the whole app (see [SampleSDK]).
final MoneyHashSDK _sdk = SampleSDK.shared;
/// What the last embed run ended with; null until one has finished.
String? _renderFormResult;
@override
void dispose() {
_intentIdController.dispose();
_intentSecretController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Checkout'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildIntentField(_intentIdController, 'Enter Intent ID'),
const SizedBox(height: 8),
_buildIntentField(_intentSecretController, 'Enter Intent Secret'),
const SizedBox(height: 20),
// Walks the intent through every state the SDK reports, drawing each one with the
// sample's own views — the equivalent of iOS's "Payment Scenario".
_buildActionButton('Payment Scenario', _openPaymentScenario),
// Hands the whole checkout to the MoneyHash embed in one call, skipping the native
// methods UI: what an integrator does when MoneyHash draws everything.
_buildActionButton('Render Form Scenario', _runRenderForm),
if (_renderFormResult != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
_renderFormResult!,
style: const TextStyle(fontSize: 13),
),
),
// Every one-off call the sample can make, on its own screen.
_buildActionButton('Scenarios', _openScenarios),
// Kills the app on purpose, from the sample rather than the SDK: the crash handler the
// SDK installs is process-wide, so a throw from here exercises it the same way, and the
// SDK ships no API that can kill a host app. Whatever the SDK had buffered must be on
// disk on the next launch.
_buildActionButton(
'Simulate Crash (telemetry flush test)',
() => throw StateError(
'Simulated crash to verify the crash log flush'),
color: Colors.black,
),
],
),
),
);
}
void _openPaymentScenario() {
if (_intentId.isEmpty) return;
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PaymentScreen(
intentId: _intentId,
intentSecret: _intentSecret,
),
),
);
}
void _openScenarios() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => ScenariosScreen(
initialIntentId: _intentId,
initialIntentSecret: _intentSecret,
),
),
);
}
Future<void> _runRenderForm() async {
if (_intentId.isEmpty) return;
final intentId = _intentId;
_recordRenderFormResult(null);
print('📱 Starting render form with intent: $intentId');
_sdk.setIntentSecret(_intentSecret);
try {
final IntentDetails? result = await _sdk.renderForm(
intentId,
IntentType.payment,
null,
);
_recordRenderFormResult(result == null
? 'Render form dismissed without a result'
: 'Render form finished: status=${result.intent?.status}, '
'state=${result.intentState?.runtimeType}');
} catch (error) {
_recordRenderFormResult('Render form failed: $error');
}
}
void _recordRenderFormResult(String? text) {
if (text != null) print(text);
if (mounted) setState(() => _renderFormResult = text);
}
/// Rebuilds on every keystroke so anything reading the getters stays in step with what is typed.
Widget _buildIntentField(TextEditingController controller, String label) {
return TextField(
controller: controller,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
labelText: label,
hintText: label,
filled: true,
border: const OutlineInputBorder(),
isDense: true,
),
);
}
Widget _buildActionButton(String title, VoidCallback onPressed,
{Color color = const Color(0xFFD62127)}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Text(title, style: const TextStyle(fontSize: 16)),
),
);
}
}