Quickstart

The drop-in widget is the whole integration. If you need your own layout, drive RezolvePayCardPaymentController directly — it exposes the same stages the widget renders.

Drop-in widget

import 'package:flutter/material.dart';
import 'package:rezolvepay_flutter/rezolvepay_flutter.dart';

void main() {
  // Your RezolvePay endpoint. Call this once, before any
  // RezolvePay widget is mounted.
  RezolvePay.configure(
    baseUrl: 'https://pay.rezolve.com',

    // Only for SDK-side minting. A merchant-wide credential: fine for
    // an internal or single-tenant app, not for a public one - mint on
    // a backend there and pass widgetToken instead.
    merchantId: 'your-merchant-id',
    merchantApiKey: const String.fromEnvironment('RP_MERCHANT_API_KEY'),
  );
  runApp(const MyApp());
}

class CheckoutScreen extends StatelessWidget {
  const CheckoutScreen({
    super.key,
    required this.order,
    required this.email,
  });

  /// The order to charge. One mount = one payment.
  final RezolvePayOrder order;
  final String email;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Checkout')),
      body: SingleChildScrollView(
        child: RezolvePayCardForm(
          // Or widgetToken: '...', if your backend minted one.
          order: order,

          // Saved cards are looked up by this. Omit it and the shopper
          // gets no saved-card pills.
          customerEmail: email,

          // Absolute https page YOU serve. Omit it and pay by bank is
          // not offered. App schemes are rejected by the API.
          returnUrl: 'https://shop.example.com/checkout/return',

          // Fires ONLY after RezolvePay itself records the payment as
          // confirmed - not when the provider says so.
          onSuccess: (RezolvePayPayment payment) {
            fulfilOrder(payment.paymentId);
          },

          onFailure: (RezolvePayError error) {
            if (error.needsReconciliation) {
              // The outcome is UNKNOWN, not failed. Do not tell the
              // shopper it failed and do not re-charge - reconcile
              // error.paymentId on your server instead.
              return;
            }
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text(error.message)),
            );
          },

          onContinueToShop: () => Navigator.of(context).pop(),
        ),
      ),
    );
  }
}

Custom UI

// Own layout? Drive the controller and render whatever you like.
final controller = RezolvePayCardPaymentController(
  // Exactly one of these: an order, or a backend-minted widgetToken.
  order: order,
  customerEmail: email,
  returnUrl: 'https://shop.example.com/checkout/return',
);

controller.addListener(() => setState(() {}));

// Lookup + provider configuration. The token is NOT spent here, so a
// shopper who abandons the form keeps a usable token.
await controller.start();

// controller.stage:  idle | loading | ready | paying | polling |
//                    succeeded | failed
// controller.screen: savedCards | newCard | manageCards | bankReview

// --- New card --------------------------------------------------
controller.updateCard(complete: true);  // fed by the native card field
controller.setCardholderName('A Shopper');
controller.setPostalCode('W1T 3JB');
controller.setSaveCard(true);
await controller.pay();   // checkout + confirm + 3DS + poll

// --- Saved card ------------------------------------------------
if (controller.hasSavedMethods) {
  await controller.payWithSavedMethod(controller.savedMethods.first.id);
}

// --- Pay by bank -----------------------------------------------
if (controller.payByBankAvailable) {
  controller.showBankReview();
  await controller.payByBank();
}

// --- Edit Payment Methods --------------------------------------
controller.showManageCards();
await controller.removeSavedMethod('spm_1');
await controller.setDefaultSavedMethod('spm_2');

controller.dispose();

Backend-minted token

import 'dart:convert';
import 'package:http/http.dart' as http;

// Your app asks YOUR server for a token, over your own authenticated
// endpoint. X-Merchant-Api-Key must NEVER ship inside the binary.
Future<String> fetchWidgetToken(String orderId) async {
  final http.Response response = await http.post(
    Uri.parse('https://shop.example.com/api/checkout-token'),
    headers: <String, String>{
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $appSessionToken',
    },
    body: jsonEncode(<String, String>{'order_id': orderId}),
  );
  if (response.statusCode != 200) {
    throw Exception('could not mint a widget token');
  }
  final Map<String, dynamic> body =
      jsonDecode(response.body) as Map<String, dynamic>;
  return body['widget_token'] as String;
}

// On your server that endpoint does POST /api/v1/checkout/init with the
// merchant credentials - the SAME call the web integration makes. See
// the Node.js example under Widget integration above.

Did this page help you?