# Flutter SDK

Official Flutter SDK for integrating ZevPay Checkout in mobile apps. Opens the standard checkout page in a WebView and returns the result to your app.

## Installation

Add to your `pubspec.yaml`:

```yaml
dependencies:
  zevpay_checkout: ^1.0.0
```

Then run:

```bash
flutter pub get
```

Requires Flutter 3.10+ and Dart 3.0+.

## Quick start

```dart
import 'package:zevpay_checkout/zevpay_checkout.dart';

final result = await ZevPayCheckout.open(
  context: context,
  options: CheckoutOptions(
    apiKey: 'pk_test_your_public_key',
    email: 'customer@example.com',
    amount: 500000, // ₦5,000 in kobo
  ),
);

if (result.isSuccess) {
  print('Payment successful! Ref: ${result.reference}');
}
```

## How it works

1. `ZevPayCheckout.open()` calls the initialize endpoint to create a session
2. Opens the ZevPay standard checkout page in a WebView
3. Customer selects a payment method and completes payment
4. SDK intercepts the callback redirect and returns a `CheckoutResult`

::: warning Public keys only
The Flutter SDK only accepts public keys (`pk_live_*` or `pk_test_*`). Secret keys must not be used in mobile apps.
:::

## Open checkout

```dart
try {
  final result = await ZevPayCheckout.open(
    context: context,
    options: CheckoutOptions(
      apiKey: 'pk_test_your_public_key',
      email: 'customer@example.com',
      amount: 500000,
      currency: 'NGN',
      reference: 'ORDER-123',
      firstName: 'John',
      lastName: 'Doe',
      metadata: {'order_id': '123'},
      paymentMethods: ['bank_transfer', 'payid'],
      theme: CheckoutTheme(
        appBarColor: Colors.teal,
        appBarTitle: 'Pay Now',
      ),
    ),
  );

  switch (result.status) {
    case CheckoutStatus.success:
      // Verify on your server, then fulfill the order
      await verifyOnServer(result.reference!);
      break;
    case CheckoutStatus.cancelled:
      print('User cancelled');
      break;
    case CheckoutStatus.expired:
      print('Session expired');
      break;
    case CheckoutStatus.error:
      print('Error: ${result.error?.message}');
      break;
  }
} on CheckoutError catch (e) {
  print('Initialization failed: ${e.message}');
}
```

## CheckoutOptions

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `apiKey` | `String` | Yes | — | Public API key (`pk_live_*` or `pk_test_*`) |
| `email` | `String` | Yes | — | Customer's email address |
| `amount` | `int` | Yes | — | Amount in kobo (min 10000 = ₦100) |
| `currency` | `String` | No | `'NGN'` | Currency code |
| `reference` | `String?` | No | Auto | Your transaction reference |
| `firstName` | `String?` | No | — | Customer's first name |
| `lastName` | `String?` | No | — | Customer's last name |
| `metadata` | `Map?` | No | — | Custom metadata |
| `paymentMethods` | `List<String>?` | No | All | `['bank_transfer', 'payid']` |
| `theme` | `CheckoutTheme?` | No | Default | Visual customization |

## CheckoutResult

| Property | Type | Description |
|----------|------|-------------|
| `status` | `CheckoutStatus` | `success`, `cancelled`, `expired`, or `error` |
| `reference` | `String?` | Transaction reference (success only) |
| `sessionId` | `String` | Session ID |
| `error` | `CheckoutError?` | Error details (error only) |
| `isSuccess` | `bool` | Convenience getter |
| `isCancelled` | `bool` | Convenience getter |

## Customize the checkout screen

Control the appearance of the WebView screen:

```dart
CheckoutTheme(
  appBarColor: Colors.indigo,           // App bar background
  appBarForegroundColor: Colors.white,  // Title and icon color
  appBarTitle: 'Complete Payment',       // Title text
  showAppBar: true,                      // Show/hide app bar
  showLoadingIndicator: true,            // Progress bar in app bar
  loadingWidget: MyCustomSpinner(),      // Custom loading widget
)
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `appBarColor` | `Color?` | ZevPay teal | Background color |
| `appBarForegroundColor` | `Color?` | White | Title and icon color |
| `appBarTitle` | `String?` | `"ZevPay Checkout"` | Title text |
| `showAppBar` | `bool` | `true` | Show/hide the app bar |
| `showLoadingIndicator` | `bool` | `true` | Show progress bar while loading |
| `loadingWidget` | `Widget?` | Default spinner | Custom loading widget |

## Verify payment

After a successful checkout, verify the payment on your **server** using a secret key:

```dart
// Dart backend only — never use secret keys in mobile apps
final verified = await ZevPayCheckout.verify(
  secretKey: 'sk_test_your_secret_key',
  sessionId: result.sessionId,
);

if (verified.isCompleted) {
  print('Confirmed! Paid at: ${verified.paidAt}');
  print('Method: ${verified.paymentMethod}');
}
```

::: warning Server-side only
`verify()` requires a secret key and should only be called from your Dart backend (dart_frog, shelf, etc.). For mobile apps, call your own backend API to verify the payment.
:::

## Webhook verification

Verify incoming webhook signatures on your Dart backend:

```dart
import 'dart:io';
import 'package:zevpay_checkout/zevpay_checkout.dart';

final isValid = ZevPayWebhook.verifySignature(
  payload: requestBody,
  signature: request.headers['x-zevpay-signature']!,
  webhookSecret: Platform.environment['ZEVPAY_WEBHOOK_SECRET']!,
);

if (isValid) {
  final event = jsonDecode(requestBody);
  switch (event['event']) {
    case 'charge.success':
      // Handle successful payment
      break;
    case 'transfer.success':
      // Handle successful transfer
      break;
  }
}
```

::: warning Always verify
Never process webhook events without verifying the signature. See [Verifying Signatures](/webhooks/signatures) for details.
:::

## Error handling

`ZevPayCheckout.open()` throws `CheckoutError` for initialization failures. Cancellation and expiry are returned in the `CheckoutResult`, not as exceptions.

```dart
try {
  final result = await ZevPayCheckout.open(...);
  // result.status is success, cancelled, expired, or error
} on CheckoutError catch (e) {
  // Initialization failed
  print('${e.code}: ${e.message}');
}
```

| Error code | When |
|------------|------|
| `MISSING_API_KEY` | No API key provided |
| `INVALID_API_KEY` | Secret key used instead of public key |
| `MISSING_EMAIL` | No email provided |
| `INVALID_AMOUNT` | Amount below 10,000 kobo |
| `INITIALIZATION_FAILED` | API returned an error |
| `NETWORK_ERROR` | Network or timeout error |
| `API_ERROR` | Server returned non-2xx status |

## Full example

```dart
import 'package:flutter/material.dart';
import 'package:zevpay_checkout/zevpay_checkout.dart';

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

  @override
  State<PaymentPage> createState() => _PaymentPageState();
}

class _PaymentPageState extends State<PaymentPage> {
  Future<void> _pay() async {
    try {
      final result = await ZevPayCheckout.open(
        context: context,
        options: CheckoutOptions(
          apiKey: 'pk_test_your_public_key',
          email: 'customer@example.com',
          amount: 500000,
          reference: 'order_${DateTime.now().millisecondsSinceEpoch}',
          theme: const CheckoutTheme(
            appBarTitle: 'Complete Payment',
          ),
        ),
      );

      if (!mounted) return;

      if (result.isSuccess) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Paid! Ref: ${result.reference}')),
        );
        // Call your backend to verify and fulfill the order
      } else if (result.isCancelled) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Payment cancelled')),
        );
      }
    } on CheckoutError catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: ${e.message}')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Checkout')),
      body: Center(
        child: ElevatedButton(
          onPressed: _pay,
          child: const Text('Pay ₦5,000'),
        ),
      ),
    );
  }
}
```

## Platform support

| Platform | Support |
|----------|---------|
| Android | Supported |
| iOS | Supported |
| Web | Not supported — use [Inline Checkout](/sdks/inline) or [React](/sdks/react) |
| Desktop | Not supported |

## Resources

- [API Reference](/api/overview) — Full endpoint documentation
- [Webhook Events](/webhooks/events) — All event types
- [pub.dev](https://pub.dev/packages/zevpay_checkout) — Package registry
