> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reeple.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter

> Integrate the Reeple checkout via a WebView bridge

<Warning>
  `@reeple/sdk` is a browser/DOM-based library (it renders a modal and iframe) — it does not run inside Flutter's Dart runtime. There is no native Reeple SDK for Flutter. The pattern below is the standard workaround used for web-based checkout SDKs on mobile: load a small hosted HTML page in a `WebView` and bridge its events back to Dart via a JavaScript channel.
</Warning>

## 1. Host a bridge HTML page

Host this page yourself (e.g. as a static file on your own domain) — it embeds the CDN script and posts SDK events back through a JavaScript channel named `ReepleChannel`.

```html reeple-checkout.html theme={null}
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /></head>
<body>
  <script src="https://payment.reeple.ai/reeple.iife.min.js"></script>
  <script>
    function post(type, payload) {
      ReepleChannel.postMessage(JSON.stringify({ type, payload }));
    }

    const params = new URLSearchParams(window.location.search);

    const reeple = new Reeple({
      publicKey: params.get('publicKey'),
      amount: Number(params.get('amount')),
      currency: params.get('currency'),
      narration: params.get('narration'),
      customer: {
        email: params.get('email'),
        firstName: params.get('firstName'),
        lastName: params.get('lastName'),
        phoneNumber: params.get('phoneNumber'),
      },
      meta: {},
      callbackUrl: window.location.href,
      onSuccess: (data) => post('success', data),
      onClose: () => post('close'),
      onError: (err) => post('error', { message: err.message }),
    });

    reeple.open();
  </script>
</body>
</html>
```

Pass the payment details as query params when loading the page from the `WebView` (see below).

## 2. Load it from Flutter

```yaml pubspec.yaml theme={null}
dependencies:
  webview_flutter: ^4.0.0
```

```dart checkout_screen.dart theme={null}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<CheckoutScreen> createState() => _CheckoutScreenState();
}

class _CheckoutScreenState extends State<CheckoutScreen> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();

    final checkoutUrl = Uri.parse('https://yoursite.com/reeple-checkout.html').replace(
      queryParameters: {
        'publicKey': 'pk_live_your_key_here',
        'amount': '5000',
        'currency': 'NGN',
        'narration': 'Order #1234',
        'email': 'customer@example.com',
        'firstName': 'John',
        'lastName': 'Doe',
        'phoneNumber': '+2348012345678',
      },
    );

    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        'ReepleChannel',
        onMessageReceived: (message) {
          final data = jsonDecode(message.message);
          switch (data['type']) {
            case 'success':
              debugPrint('Payment successful: ${data['payload']}');
              break;
            case 'close':
              debugPrint('Modal closed');
              break;
            case 'error':
              debugPrint('Payment error: ${data['payload']['message']}');
              break;
          }
        },
      )
      ..loadRequest(checkoutUrl);
  }

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(controller: controller);
  }
}
```

<Note>
  Verify the payment on your server using the `reference` from the success payload — see [Callbacks & Verification](/callbacks-and-verification). Never mark an order paid based solely on the WebView message.
</Note>
