peachpayments_flutter 1.5.3
peachpayments_flutter: ^1.5.3 copied to clipboard
Peach Payments SDK for Flutter — accept card and alternative payments in your Flutter app with a prebuilt, customisable payment sheet.
peachpayments_flutter #
Peach Payments SDK for Flutter — accept card and alternative payments in your Flutter app with a prebuilt, customisable payment sheet.
Wraps Peach Payments' native Android and iOS SDKs behind a single Dart API.
⚠️ Setup is not just flutter pub add #
Peach Payments' native artefacts are not published to Maven Central or the CocoaPods trunk. Each platform needs one extra repository declared, or your first build will fail:
| Platform | What you need |
|---|---|
| Android | The Peach Maven registry. No credentials required — it allows anonymous reads, and apply_plugins writes it in for you |
| iOS | The Peach CocoaPods spec repo added as a source in your Podfile |
Both are covered in Installation below.
Installation #
1. Add the dependency #
dependencies:
peachpayments_flutter: ^1.0.0
Optional feature packages — add only what you need, each links an extra native dependency:
peachpayments_flutter_netcetera_3ds: ^1.0.0 # Netcetera 3-D Secure
peachpayments_flutter_scancard: ^1.0.0 # card scanning
Then flutter pub get.
2. Android #
Run the helper once. It writes the Gradle plugin and the required Maven repositories into your app's Gradle files:
dart run peachpayments_flutter:apply_plugins
This is a manual step — pub has no post-get hook, so it cannot run automatically. It is safe to re-run: the script is idempotent and will also upgrade an older Gradle plugin version in place.
That is all that is needed. The registry
(https://gitlab.com/api/v4/projects/81506485/packages/maven) allows anonymous reads, so
no token or credentials are required.
Two optional overrides exist for unusual setups:
| Property | When you need it |
|---|---|
PEACH_MAVEN_URL |
Point at a different project's registry |
PEACH_MAVEN_TOKEN |
Only if that registry requires authentication |
If you do use a token, keep it out of version control — put it in
~/.gradle/gradle.properties or inject it from CI secrets, not the app's
android/gradle.properties.
If your app routes dependency resolution through settings.gradle
(dependencyResolutionManagement with FAIL_ON_PROJECT_REPOS or PREFER_SETTINGS),
Gradle ignores the repositories in android/build.gradle — apply_plugins will warn and
print the block to add there instead.
The SDK requires minSdk = 24; apply_plugins sets this for you.
3. iOS #
Add the Peach spec repo to the top of ios/Podfile. A podspec cannot declare its own
sources, so every consuming app needs these two lines:
source 'https://github.com/peach-payments/hyperswitch-sdk-ios.git'
source 'https://cdn.cocoapods.org/'
platform :ios, '15.1'
Then cd ios && pod install. Minimum deployment target is iOS 15.1.
Usage #
import 'package:peachpayments_flutter/peachpayments_flutter.dart';
final _peach = PeachPayments();
// 1. Configure the SDK with your publishable key.
_peach.init(PeachPaymentsConfig(publishableKey: publishableKey));
// 2. Open a session against a client secret from your backend.
final session = await _peach.initPaymentSession(
PaymentMethodParams(
clientSecret: clientSecret,
configuration: Configuration(
merchantDisplayName: 'My Store',
primaryButtonLabel: 'Pay',
),
),
);
// 3. Present the payment sheet.
final result = await _peach.presentPaymentSheet(session);
switch (result.status) {
case Status.completed:
// payment succeeded
break;
case Status.cancelled:
// customer dismissed the sheet
break;
case Status.failed:
debugPrint(result.error.message);
break;
}
Errors are surfaced as PeachPaymentsException:
try {
final session = await _peach.initPaymentSession(params);
} on PeachPaymentsException catch (e) {
debugPrint('${e.code}: ${e.message}');
}
Saved payment methods (headless) #
To charge a returning customer without showing the sheet:
final saved = await _peach.getCustomerSavedPaymentMethods(session);
// Inspect the default or last-used method before charging.
final method = await _peach.getCustomerLastUsedPaymentMethodData(saved);
if (method is PaymentMethod) {
final result = await _peach.confirmWithLastUsedPaymentMethod(saved);
}
iOS limitation on
confirmWithLastUsedPaymentMethod. If the saved card requires a CVC (requiresCvv), iOS cannot confirm it headlessly — the SDK needs aCVCWidgetthe customer types into. The call fails with a clear message; usepresentPaymentSheetfor those cards. Android accepts a null CVC and has no such restriction.
Available calls:
| Method | Returns |
|---|---|
init(PeachPaymentsConfig) |
— |
initPaymentSession(PaymentMethodParams) |
Session |
presentPaymentSheet(Session) |
PaymentResult |
getCustomerSavedPaymentMethods(Session) |
SavedSession |
getCustomerDefaultSavedPaymentMethodData(SavedSession) |
PaymentMethodResponse |
confirmWithCustomerDefaultPaymentMethod(SavedSession) |
PaymentResult |
getCustomerLastUsedPaymentMethodData(SavedSession) |
PaymentMethodResponse |
confirmWithLastUsedPaymentMethod(SavedSession) |
PaymentResult |
Customising the sheet #
Configuration.appearance accepts an Appearance object covering layout, colours,
fonts, and the Google Pay / Apple Pay button styles:
Configuration(
appearance: Appearance(
layout: Layout.spacedAccordion,
font: Font(family: 'Montserrat'),
colors: DynamicColors(
light: ColorsObject(primary: '#8DBD00', background: '#F5F8F9'),
dark: ColorsObject(primary: '#8DBD00', background: '#F5F8F9'),
),
primaryButton: PrimaryButton(shapes: Shapes(borderRadius: 32.0)),
),
)
Custom fonts must also be declared in your app's pubspec.yaml fonts: section — the
native side loads them by family name from your Flutter assets.
Saved payment methods #
Two independent switches control what a returning shopper is offered:
Configuration(
displaySavedPaymentMethods: false, // hide saved methods entirely
displayAddNewPaymentMethod: false, // keep the shopper on their saved methods
)
displayAddNewPaymentMethod: false removes the "Add new payment method" link from
the saved-methods screen. It cannot strand a shopper: when there is nothing saved to
stay on, the sheet shows the new-payment form regardless of this setting.
Both default to true.
Billing details #
The payment sheet decides which billing fields to show from your connector
configuration, not from anything this package sends. The SDK combines the
required_fields returned by /account/payment_methods with a remote field
definition, and renders a field when it is marked required and your backend did
not already supply a non-empty value for it.
Two consequences worth knowing:
Configuration.defaultBillingDetailsneither prefills nor hides anything. The native SDK parses it and then never reads it. It is kept for API compatibility; the gap is tracked inreferences/bug-report-default-billing-details-inert.md.- Visibility is all-or-nothing per group. If any one sub-field of the billing address is missing a value, the whole address block is shown. The same applies to the first-name/last-name pair and to the phone pair.
So if billing fields appear when you expect them not to, the fix is to return a
non-empty value for every field in that group from your
/account/payment_methods response. No client-side option overrides this.
3-D Secure #
3DS is handled entirely by the native SDK; this package does not see the
next_action on a confirm response. There are two distinct flows, and which one
you get is decided by your connector and the issuer:
next_action.type |
Flow | What it needs |
|---|---|---|
three_ds_invoke |
Native app-based challenge (Netcetera) | the peachpayments_flutter_netcetera_3ds dependency and Configuration.netceteraSDKApiKey |
redirect_to_url |
Browser redirect in an authentication session | nothing extra |
redirect_inside_popup |
Browser redirect in an authentication session | nothing extra |
| anything else | Browser redirect in an authentication session | nothing extra |
These do not fall back to one another. If your connector returns
three_ds_invoke and the Netcetera dependency is absent, the payment fails with
an integration error — the browser redirect is never attempted.
If you support app-based 3DS, add both:
dependencies:
peachpayments_flutter_netcetera_3ds: ^1.1.2
Configuration(netceteraSDKApiKey: '<your Netcetera API key>')
Before 1.3.0 this could not work on iOS at all. The whole configuration object was being dropped there, so
netceteraSDKApiKeynever reached the sheet. If you are on an earlier version and your connector returnsthree_ds_invoke, upgrading is the fix.
Before 1.3.0 the
redirect_inside_popupshape never opened a browser. The native SDK read the redirect URL out ofnext_action.redirect_to_urlunconditionally, but that shape carries it inpopup_urlinstead, so the URL came back empty and the authentication session was never presented — no browser, no error. Whether you hit this was decided entirely by which shape your connector returns, which is why it looked like a device-versus-simulator problem. Fixed inpeachpayments-hyperswitch-ios0.7.3 /io.peachpayments:hyperswitch-sdk-android1.5.4, which this version pins. Seereferences/bug-report-3ds-redirect-silent-failure.md.
Platform differences #
| Android | iOS | |
|---|---|---|
PaymentMethodParams.ephemeralKey |
✅ | ❌ — see below |
PaymentMethodParams.customParams |
✅ | ❌ |
iOS SDK 0.7.x builds the payment sheet's top-level props itself and gives a
wrapper SDK control only over the configuration object, so there is no way for
this package to supply ephemeralKey or customParams on iOS. Customer-scoped
saved-payment-method flows that depend on an ephemeral key are Android-only until
that is fixed upstream; it is written up in
references/bug-report-ios-params-contract.md.
Configuration.customer.ephemeralKeySecret is not an alternative — the native
SDK's decoder for that object is commented out on both platforms.
Diagnostics #
Keep telemetry switched on #
The SDK resolves its logging endpoint from the pair (customBackendUrl,
customLogUrl). Setting a custom backend without a custom log URL resolves to
"no endpoint", and every log event is silently discarded — including the events
that would explain a failed payment. This is the usual configuration for a
white-labelled integration, so it is easy to end up with an SDK that reports
nothing at all.
_peach.init(PeachPaymentsConfig(
publishableKey: publishableKey,
customBackendUrl: 'https://<your-backend>/api',
customLogUrl: 'https://<your-backend>/api/logs/sdk', // <- required, or no logs
));
The package logs a warning if it sees the first without the second.
debugLogging #
Configuration(debugLogging: true) makes both platform plugins print the
parameter set they hand to the native sheet — which top-level and configuration
keys actually crossed the platform channel, and whether appearance,
displaySavedPaymentMethods and netceteraSDKApiKey arrived.
It is opt-in and intended for integration problems you cannot attach a debugger to. Because neither this plugin nor the native SDK decodes the configuration field by field, "did the key reach the SDK at all" is usually the first useful question. Leave it off in production.
Which JavaScript bundle is running (iOS) #
The iOS SDK is React Native underneath, so it is worth knowing where its JavaScript comes from when triaging "works on the simulator, not on the device".
Leave HyperswitchSource unset. With it unset and without the airborne
package, the SDK loads the bundle compiled into its own framework — the same
bytes for every install of a given pod version, so it cannot be the source of a
device-versus-simulator difference. Over-the-air updates are compiled in only
when HyperOTA is linked, which happens solely via the airborne subspec.
Do not set the key to LocalBundle in a host app: that branch resolves the
bundle from Bundle.main, while the pod delivers it inside the framework
bundle, so the lookup returns nil and the sheet fails to load. LocalHosted
points at a Metro dev server. Both exist for SDK development, not integration.
debugLogging reports the key's value so you can confirm it is unset.
Payment method logos #
The package bundles SVG logos for the payment methods Peach supports, so you can build your own method pickers or summaries without shipping the artwork yourself:
PeachPaymentMethodLogo(PeachPaymentMethod.capitecPay, height: 24)
// Or straight from an API key such as `pay_shap`; unknown keys render `fallback`.
PeachPaymentMethodLogo.fromKey('pay_shap', height: 24, fallback: Icon(Icons.payment))
// Apple Pay has a white variant for dark backgrounds.
PeachPaymentMethodLogo(PeachPaymentMethod.applePay, height: 24, onDark: true)
PeachPaymentMethod.logoAssetPath() returns the raw asset path if you want to load it
with SvgPicture.asset(path, package: 'peachpayments_flutter') yourself. Logos are
available for: capitec_pay, pay_shap, peach_eft, payflex, zero_pay, float,
happy_pay, mobicred, a_plus, rcs, payjustnow, apple_pay, scan_to_pay, one_for_you, google_pay,
money_badger, blink_by_emtel, maucas, mcb_juice, mpesa.
Example #
See apps/example
for a complete app, including a sample backend for creating payment intents.
Migrating from flutter_hyperswitch #
This package replaces the upstream flutter_hyperswitch package. The public API is the
same shape; the names changed:
| Before | After |
|---|---|
package:flutter_hyperswitch/flutter_hyperswitch.dart |
package:peachpayments_flutter/peachpayments_flutter.dart |
FlutterHyperswitch |
PeachPayments |
HyperConfig |
PeachPaymentsConfig |
HyperParams |
PeachPaymentsParams |
HyperswitchException |
PeachPaymentsException |
FlutterHyperswitchPlatform |
PeachPaymentsPlatform |
MethodChannelFlutterHyperswitch |
MethodChannelPeachPayments |
Method names and payment-domain types (Session, PaymentResult, Configuration,
Appearance, …) are unchanged. The native dependencies now resolve to Peach Payments'
io.peachpayments Android artefacts and peachpayments-hyperswitch-ios pods, which is
why the extra repository setup above is required.
Licence #
Apache-2.0 — see LICENSE and NOTICE. Derived from Juspay's Hyperswitch client SDK.