> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coinflow.cash/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coinflow.cash/_mcp/server.

# Flutter SDK

## Overview

`coinflow_card_form` is a Flutter SDK that embeds Coinflow's card tokenization form directly into iOS and Android apps from a single Dart codebase. The user enters their card inside your app, the SDK returns a payment token, and your backend charges the card via the standard Coinflow checkout API.

* **Package:** [`coinflow_card_form`](https://pub.dev/packages/coinflow_card_form)
* **Distribution:** pub.dev
* **Current version:** `0.2.0`

## Requirements

* Flutter 3.x+
* iOS 15+ / Android `minSdk` 24+

## Installation

```bash
flutter pub add coinflow_card_form
```

Or in `pubspec.yaml`:

```yaml
dependencies:
  coinflow_card_form: ^0.2.0
```

## Integration

#### Add the card form widget

Drop `CoinflowCardFormWidget` into your widget tree and pass a `CoinflowCardFormController` you'll use to trigger tokenization.

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

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

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

class _PaymentPageState extends State<PaymentPage> {
  final _controller = CoinflowCardFormController();

  Future<void> _tokenize() async {
    try {
      final response = await _controller.tokenize();
      debugPrint('Token: ${response.token}');
    } catch (e) {
      // surface error to user
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        CoinflowCardFormWidget(
          variant: CardFormVariant.cardForm,
          merchantId: 'your-merchant-id',
          env: CoinflowEnv.sandbox,
          controller: _controller,
        ),
        FilledButton(onPressed: _tokenize, child: const Text('Pay')),
      ],
    );
  }
}
```

> **Info**
>
> `your-merchant-id` is an **example placeholder**. Use your actual merchant ID from the merchant dashboard, or contact the Coinflow integrations team. Typically injected with `--dart-define=COINFLOW_MERCHANT_ID=...`, not hard-coded.

#### Wait for the form to load

Call `controller.tokenize()` only after the form has loaded. Either pass an `onLoad` callback or check `controller.isLoaded` before invoking:

```dart
final _controller = CoinflowCardFormController(onLoad: () {
  setState(() => _ready = true);
});
```

Invoking `tokenize()` earlier throws `CoinflowException('Card form not yet loaded')`. Concurrent calls also throw — only one tokenize can be in flight at a time.

#### Configure the environment

```dart
const env = bool.fromEnvironment('dart.vm.product')
    ? CoinflowEnv.prod
    : CoinflowEnv.sandbox;
```

* `CoinflowEnv.sandbox` — test cards, no real money
* `CoinflowEnv.prod` — live cards, real money

#### Charge the token server-side

`controller.tokenize()` returns a `Future<TokenizeResponse>`:

* `token` — payment token to send to your backend
* `expMonth`, `expYear` — populated only for variants that collect expiry

Send the token to your server and call Coinflow's checkout API to charge it. See the [Checkout API reference](/api-reference/api-reference/checkout/card-checkout) for the full request shape.

## Variants

| Variant                          | Captures            | Use case                                   |
| -------------------------------- | ------------------- | ------------------------------------------ |
| `CardFormVariant.cardForm`       | Number, expiry, CVV | Standard one-shot capture                  |
| `CardFormVariant.cardNumberForm` | Number + expiry     | First step of a two-step flow              |
| `CardFormVariant.cvvForm`        | CVV only            | Re-collecting CVV for a card-on-file token |

## Theming

`MerchantTheme` styles the rendered form. All fields optional.

```dart
const theme = MerchantTheme(
  primary: '#165DFB',
  background: '#ffffff',
  textColor: '#05092E',
  ctaColor: '#165DFB',
  font: 'Red Hat Display',
  style: MerchantStyle.rounded,
);

CoinflowCardFormWidget(theme: theme, /* ... */)
```

#### All theme fields

| Field                                                              | Purpose                                             |
| ------------------------------------------------------------------ | --------------------------------------------------- |
| `primary`, `ctaColor`                                              | Accent / action colors (hex strings)                |
| `background`, `backgroundAccent`, `backgroundAccent2`              | Form background tones                               |
| `textColor`, `textColorAccent`, `textColorAction`                  | Input and label text colors                         |
| `font`, `fontSize`, `fontWeight`                                   | Typography. `font` must be available on the device. |
| `style`                                                            | Input shape: `rounded`, `sharp`, `pill`             |
| `cardNumberPlaceholder`, `cvvPlaceholder`, `expirationPlaceholder` | Override input placeholder text                     |
| `showCardIcon`                                                     | Toggle the card brand icon (Visa/Mastercard/Amex)   |

## Dynamic height

The hosted form reflows responsively — at narrow widths the inputs wrap to multiple rows. To keep your Flutter container fitted, pass an `onHeightChange` callback to the controller and drive `CoinflowCardFormWidget.height` from it:

```dart
double _formHeight = 52;

late final _controller = CoinflowCardFormController(
  onHeightChange: (h) => setState(() => _formHeight = h),
);

@override
Widget build(BuildContext context) {
  return CoinflowCardFormWidget(
    merchantId: 'your-merchant-id',
    controller: _controller,
    height: _formHeight,
  );
}
```

The callback receives the rendered content height in logical pixels. Without this wiring the form may be clipped if it wraps.

## Resources

* **Source:** [github.com/coinflow-labs-us/coinflow-flutter](https://github.com/coinflow-labs-us/coinflow-flutter)
* **pub.dev:** [coinflow\_card\_form](https://pub.dev/packages/coinflow_card_form)
* **License:** Apache 2.0
* **Checkout API:** [Card Checkout endpoint](/api-reference/api-reference/checkout/card-checkout)