Skip to main content

esaalat 0.4.0 copy "esaalat: ^0.4.0" to clipboard
esaalat: ^0.4.0 copied to clipboard

Hosted checkout for Flutter — a ready-made pay button, a full-screen WebView, and verified payment results.

example/lib/main.dart

import 'package:esaalat/esaalat.dart';
import 'package:flutter/material.dart';

/// Pass your key at build time:
///
/// ```bash
/// flutter run --dart-define=ESAALAT_PUBLISHABLE_KEY=pk_...
/// ```
const String _publishableKey = String.fromEnvironment(
  'ESAALAT_PUBLISHABLE_KEY',
  defaultValue: 'pk_test_replace_me',
);

const String _apiBaseUrl = String.fromEnvironment(
  'ESAALAT_API_BASE_URL',
  defaultValue: esaalatDefaultApiBaseUrl,
);

const String _portalBaseUrl = String.fromEnvironment(
  'ESAALAT_PORTAL_BASE_URL',
  defaultValue: esaalatDefaultPortalBaseUrl,
);

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Esaalat checkout',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF0B0B0B),
        useMaterial3: true,
      ),
      home: const CheckoutDemoPage(),
    );
  }
}

class CheckoutDemoPage extends StatefulWidget {
  const CheckoutDemoPage({super.key});

  @override
  State<CheckoutDemoPage> createState() => _CheckoutDemoPageState();
}

class _CheckoutDemoPageState extends State<CheckoutDemoPage> {
  final TextEditingController _amount = TextEditingController(text: '1000');
  late final String _orderId =
      'flutter-${DateTime.now().millisecondsSinceEpoch}';

  CheckoutResult? _result;
  String? _error;

  @override
  void dispose() {
    _amount.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(title: const Text('Esaalat checkout')),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: [
          Text(
            'One button: session, checkout route and result routing.',
            style: theme.textTheme.bodyMedium,
          ),
          const SizedBox(height: 20),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Text('Order amount', style: theme.textTheme.labelLarge),
                  const SizedBox(height: 8),
                  TextField(
                    controller: _amount,
                    // Rebuild so the button reads the amount the buyer typed.
                    onChanged: (_) => setState(() {}),
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      suffixText: 'YER',
                    ),
                  ),
                  const SizedBox(height: 16),
                  Align(
                    alignment: AlignmentDirectional.centerStart,
                    child: EsaalatCheckoutButton(
                      publishableKey: _publishableKey,
                      amount: _amount.text.trim(),
                      currency: 'YER',
                      externalOrderId: _orderId,
                      apiBaseUrl: _apiBaseUrl,
                      portalBaseUrl: _portalBaseUrl,
                      onVerified: (result) => setState(() {
                        _result = result;
                        _error = null;
                      }),
                      onCancelled: (result) => setState(() => _result = result),
                      onWrongAmount: (result) =>
                          setState(() => _result = result),
                      onExpired: (result) => setState(() => _result = result),
                      onFailed: (result) => setState(() => _result = result),
                      onError: (error) => setState(
                        () => _error = '${error.message} (${error.code.name})',
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
          if (_error != null) ...[
            const SizedBox(height: 20),
            Card(
              color: theme.colorScheme.errorContainer,
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Text(
                  _error!,
                  style: theme.textTheme.bodyMedium?.copyWith(
                    color: theme.colorScheme.onErrorContainer,
                  ),
                ),
              ),
            ),
          ],
          if (_result != null) ...[
            const SizedBox(height: 20),
            _ResultCard(result: _result!),
          ],
        ],
      ),
    );
  }
}

class _ResultCard extends StatelessWidget {
  const _ResultCard({required this.result});

  final CheckoutResult result;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final verified = result.isVerified;

    return Card(
      color: verified
          ? theme.colorScheme.primaryContainer
          : theme.colorScheme.surfaceContainerHighest,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              result.status.wire,
              style: theme.textTheme.titleMedium?.copyWith(
                fontWeight: FontWeight.w700,
              ),
            ),
            if (result.reason != null)
              Text(
                'reason: ${result.reason!.wire}',
                style: theme.textTheme.bodySmall,
              ),
            const SizedBox(height: 12),
            _Row(label: 'Amount', value: '${result.amount} ${result.currency}'),
            if (result.paidAmount != null)
              _Row(
                  label: 'Paid',
                  value: '${result.paidAmount} ${result.currency}'),
            if (result.intentCode != null)
              _Row(label: 'Intent', value: result.intentCode!),
            if (result.externalOrderId.isNotEmpty)
              _Row(label: 'Order', value: result.externalOrderId),
            _Row(label: 'Session', value: result.sessionToken),
            const SizedBox(height: 12),
            Text(
              'Confirm the order server-side from the signed webhook before '
              'fulfilling it.',
              style: theme.textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }
}

class _Row extends StatelessWidget {
  const _Row({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.only(bottom: 4),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 72,
            child: Text(label, style: theme.textTheme.bodySmall),
          ),
          Expanded(
            child: SelectableText(
              value,
              style: theme.textTheme.bodyMedium,
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
735
downloads

Documentation

Documentation
API reference

Publisher

verified publisheresaalat.com

Weekly Downloads

Hosted checkout for Flutter — a ready-made pay button, a full-screen WebView, and verified payment results.

Homepage
View/report issues

Topics

#payments #checkout #flutter #yemen #mobile

License

MIT (license)

Dependencies

flutter, flutter_svg, http, url_launcher, webview_flutter

More

Packages that depend on esaalat