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.Updated about 4 hours ago
Did this page help you?

