Powered by Coinflow
Payments · Documentation
Operational

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.

Requirements

  • Flutter 3.x+
  • iOS 15+ / Android minSdk 24+

Installation

flutter pub add coinflow_card_form

Or in pubspec.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.

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')),
      ],
    );
  }
}

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:

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

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 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.

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:

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