authnet_flutter 1.0.2 copy "authnet_flutter: ^1.0.2" to clipboard
authnet_flutter: ^1.0.2 copied to clipboard

Authorize.Net payments for Flutter: Accept.js, Accept Hosted, wallet tokens, gated direct-charge client. Not affiliated with or endorsed by Authorize.Net or Visa.

example/lib/main.dart

// Runnable example demonstrating all three authnet_flutter usage modes.
// Needs a real device/simulator to exercise the WebView-based widgets (Mode
// 2); see the package README's confidence caveat on those widgets.
//
//   flutter run
//
// This app never talks to a real backend of your own. Mode 2's "charge
// server-side" step and Mode 3's sandbox credentials are both entered by
// you at runtime, so you can see each mode's shape without wiring up a
// throwaway server just to try the SDK.
import 'package:authnet_core/authnet_core.dart';
import 'package:authnet_flutter/authnet_flutter.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'authnet_flutter example',
      home: ModePickerPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('authnet_flutter example')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          const Text(
            'Not affiliated with, endorsed by, or certified by '
            'Authorize.Net or Visa.',
            style: TextStyle(fontStyle: FontStyle.italic),
          ),
          const SizedBox(height: 24),
          _ModeCard(
            title: 'Mode 2a: Accept.js',
            subtitle: 'Tokenize a card in-app, charge it from your backend.',
            onTap: () => Navigator.of(context).push(
              MaterialPageRoute<void>(builder: (_) => const AcceptJsDemoPage()),
            ),
          ),
          _ModeCard(
            title: 'Mode 2b: Accept Hosted',
            subtitle: "Authorize.Net's own hosted checkout page, embedded.",
            onTap: () => Navigator.of(context).push(
              MaterialPageRoute<void>(
                  builder: (_) => const AcceptHostedDemoPage()),
            ),
          ),
          _ModeCard(
            title: 'Mode 3: Direct charge (opt-in, risky)',
            subtitle: 'Charges directly from the app. Read the warning first.',
            onTap: () => Navigator.of(context).push(
              MaterialPageRoute<void>(
                  builder: (_) => const DirectChargeDemoPage()),
            ),
          ),
        ],
      ),
    );
  }
}

class _ModeCard extends StatelessWidget {
  const _ModeCard(
      {required this.title, required this.subtitle, required this.onTap});

  final String title;
  final String subtitle;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        title: Text(title),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.chevron_right),
        onTap: onTap,
      ),
    );
  }
}

/// Mode 2a: tokenize with Accept.js, then hand the nonce to your backend.
///
/// This page owns its own card-entry form (plain `TextField`s, styled
/// however you like) and its own `WebViewController`. authnet_flutter
/// only supplies the HTML to load and the JS call to run; wiring a WebView
/// package to it is this app's job, exactly like it would be in a real app.
class AcceptJsDemoPage extends StatefulWidget {
  const AcceptJsDemoPage({super.key});

  @override
  State<AcceptJsDemoPage> createState() => _AcceptJsDemoPageState();
}

class _AcceptJsDemoPageState extends State<AcceptJsDemoPage> {
  final _apiLoginIdController = TextEditingController();
  final _publicClientKeyController = TextEditingController();
  final _cardNumberController = TextEditingController();
  final _monthController = TextEditingController();
  final _yearController = TextEditingController();
  final _cardCodeController = TextEditingController();

  WebViewController? _webViewController;
  OpaqueData? _result;
  String? _error;

  @override
  void dispose() {
    _apiLoginIdController.dispose();
    _publicClientKeyController.dispose();
    _cardNumberController.dispose();
    _monthController.dispose();
    _yearController.dispose();
    _cardCodeController.dispose();
    super.dispose();
  }

  void _loadEngine() {
    final controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        acceptJsChannelName,
        onMessageReceived: (message) {
          switch (parseAcceptJsMessage(message.message)) {
            case AcceptJsSuccess(:final opaqueData):
              setState(() {
                _result = opaqueData;
                _error = null;
              });
            case AcceptJsFailure(:final message):
              setState(() {
                _error = message;
                _result = null;
              });
          }
        },
      )
      ..loadHtmlString(
        buildAcceptJsHtml(
          apiLoginId: _apiLoginIdController.text,
          publicClientKey: _publicClientKeyController.text,
          sandbox: true,
        ),
        // Accept.js refuses to tokenize on a page with no HTTPS origin.
        // loadHtmlString() alone has none, so a baseUrl is required. See
        // buildAcceptJsHtml()'s dartdoc.
        baseUrl: 'https://localhost',
      );
    setState(() => _webViewController = controller);
  }

  void _tokenize() {
    _webViewController?.runJavaScript(buildAcceptJsTokenizeCall(
      cardNumber: _cardNumberController.text,
      month: _monthController.text,
      year: _yearController.text,
      cardCode: _cardCodeController.text,
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Accept.js')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _apiLoginIdController,
              decoration:
                  const InputDecoration(labelText: 'Sandbox API Login ID'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _publicClientKeyController,
              decoration: const InputDecoration(
                labelText: 'Public client key',
                helperText: 'From AuthNetClient.getMerchantDetails()',
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _apiLoginIdController.text.isEmpty ||
                      _publicClientKeyController.text.isEmpty
                  ? null
                  : _loadEngine,
              child: const Text('Load the tokenization engine'),
            ),
            if (_webViewController != null) ...[
              const SizedBox(height: 16),
              TextField(
                controller: _cardNumberController,
                decoration: const InputDecoration(labelText: 'Card number'),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _monthController,
                decoration: const InputDecoration(labelText: 'Exp. month'),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _yearController,
                decoration: const InputDecoration(labelText: 'Exp. year'),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _cardCodeController,
                decoration: const InputDecoration(labelText: 'CVV'),
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: _tokenize,
                child: const Text('Tokenize'),
              ),
              // The WebView here runs Accept.js's JS but renders no UI of
              // its own. This tiny box is only so the platform actually
              // mounts and runs it. A real app can size this to zero or
              // keep it off-screen the same way.
              SizedBox(
                height: 1,
                child: WebViewWidget(controller: _webViewController!),
              ),
            ],
            if (_result != null)
              Text(
                'Tokenized. dataDescriptor: ${_result!.dataDescriptor}\n'
                'Send this OpaqueData to your backend and charge it with '
                "authnet_core's PaymentRequest(method: "
                'PaymentMethod.opaqueData, ...).',
              ),
            if (_error != null) Text('Error: $_error'),
          ],
        ),
      ),
    );
  }
}

/// Mode 2b: Authorize.Net's own hosted checkout, embedded in-app. Needs a
/// token from `AuthNetClient.getHostedPaymentPageToken()`. Get one from
/// your backend and paste it in below (this example has no backend of its
/// own to call).
class AcceptHostedDemoPage extends StatefulWidget {
  const AcceptHostedDemoPage({super.key});

  @override
  State<AcceptHostedDemoPage> createState() => _AcceptHostedDemoPageState();
}

class _AcceptHostedDemoPageState extends State<AcceptHostedDemoPage> {
  final _tokenController = TextEditingController();
  final _pageOriginController =
      TextEditingController(text: 'https://pay.example.com');
  WebViewController? _webViewController;
  String? _lastMessage;

  @override
  void dispose() {
    _tokenController.dispose();
    _pageOriginController.dispose();
    super.dispose();
  }

  void _loadHostedPage() {
    const config = AuthNetConfig(apiLoginId: '', transactionKey: '');
    final controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..addJavaScriptChannel(
        acceptHostedChannelName,
        onMessageReceived: (message) => setState(() =>
            _lastMessage = parseAcceptHostedMessage(message.message).action),
      )
      ..loadHtmlString(
        buildAcceptHostedHtml(
          token: _tokenController.text,
          formActionUrl: config.hostedFormUrl,
        ),
        baseUrl: _pageOriginController.text,
      );
    setState(() => _webViewController = controller);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Accept Hosted')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _tokenController,
              decoration: const InputDecoration(
                labelText: 'Hosted payment page token',
                helperText:
                    'From your backend: AuthNetClient.getHostedPaymentPageToken()',
              ),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: _pageOriginController,
              decoration: const InputDecoration(
                labelText: 'Communicator page origin',
                helperText: 'Must match the HTTPS origin of the communicator '
                    'URL used when requesting this token',
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _tokenController.text.isEmpty ? null : _loadHostedPage,
              child: const Text('Load the hosted page'),
            ),
            const SizedBox(height: 16),
            if (_webViewController != null)
              Expanded(
                child: WebViewWidget(controller: _webViewController!),
              ),
            if (_lastMessage != null)
              Text('Last message action: $_lastMessage'),
          ],
        ),
      ),
    );
  }
}

/// Mode 3: charge directly from the app. Read `AuthNetDirectCharge`'s
/// dartdoc before using this in a real app: this demo page's warning
/// banner is not a substitute for it.
class DirectChargeDemoPage extends StatefulWidget {
  const DirectChargeDemoPage({super.key});

  @override
  State<DirectChargeDemoPage> createState() => _DirectChargeDemoPageState();
}

class _DirectChargeDemoPageState extends State<DirectChargeDemoPage> {
  final _apiLoginIdController = TextEditingController();
  final _transactionKeyController = TextEditingController();
  bool _acknowledged = false;
  String? _resultMessage;

  @override
  void dispose() {
    _apiLoginIdController.dispose();
    _transactionKeyController.dispose();
    super.dispose();
  }

  Future<void> _charge() async {
    final direct = AuthNetDirectCharge(
      config: AuthNetConfig(
        apiLoginId: _apiLoginIdController.text,
        transactionKey: _transactionKeyController.text,
      ),
      acknowledgeClientSecretRisk: true,
    );
    final result = await direct.charge(PaymentRequest(
      amount: 1.00,
      method: PaymentMethod.creditCard,
      billing: BillingDetails(firstName: 'Test', lastName: 'Buyer'),
      // Authorize.Net's published sandbox test card (always approves).
      card: CardDetails(
          number: '4111111111111111',
          expMonth: '12',
          expYear: '2030',
          cvv: '900'),
    ));
    direct.close();
    if (!mounted) return;
    setState(() => _resultMessage = '${result.status}: ${result.message}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Direct charge (Mode 3)')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text(
              'This mode embeds your Transaction Key in the app. Only '
              'use it if you understand and accept that risk; see '
              "AuthNetDirectCharge's dartdoc.",
              style: TextStyle(color: Colors.red),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _apiLoginIdController,
              decoration:
                  const InputDecoration(labelText: 'Sandbox API Login ID'),
            ),
            const SizedBox(height: 8),
            TextField(
              controller: _transactionKeyController,
              decoration:
                  const InputDecoration(labelText: 'Sandbox Transaction Key'),
              obscureText: true,
            ),
            const SizedBox(height: 8),
            CheckboxListTile(
              value: _acknowledged,
              onChanged: (v) => setState(() => _acknowledged = v ?? false),
              title: const Text('I understand and accept the risk above.'),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _acknowledged &&
                      _apiLoginIdController.text.isNotEmpty &&
                      _transactionKeyController.text.isNotEmpty
                  ? _charge
                  : null,
              child: const Text('Charge \$1.00 sandbox test card'),
            ),
            if (_resultMessage != null) Text(_resultMessage!),
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
205
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Authorize.Net payments for Flutter: Accept.js, Accept Hosted, wallet tokens, gated direct-charge client. Not affiliated with or endorsed by Authorize.Net or Visa.

Repository (GitHub)
View/report issues
Contributing

Topics

#authorize-net #payments #flutter #accept-js #payment-gateway

License

MIT (license)

Dependencies

authnet_core, http

More

Packages that depend on authnet_flutter