Uni Payments

Ten payment gateways. One Future<PaymentResult>. Zero glue code.

A unified Flutter API over Razorpay · Stripe · PayPal · Paystack · Flutterwave · Paytm · Cashfree · PhonePe · Google Pay · Apple Pay.

pub pub points flutter dart license

final result = await UniPayments.payWithRazorpay(
  keyId: 'YOUR_RAZORPAY_KEY_ID',
  amount: 25.00,
  businessName: 'Acme Inc',
  customer: UniCustomer(name: 'Ada', email: 'ada@x.com', phone: '9999999999'),
);

switch (result) {
  case PaymentSuccess(:final transactionId): /* verify on backend */
  case PaymentFailure(:final errorCode, :final message): /* show error */
  case PaymentCancelled(): /* user dismissed the sheet */
}

Swap payWithRazorpay for payWithStripe, payWithPaypal, payWithGooglePay, … the call shape is identical for every gateway.


Why this exists

Six gateway SDKs ship six call shapes, six response objects, six ways the user can cancel. Even when you wrap them, "user closed the sheet" usually disappears into a generic catch.

Uni Payments replaces all of that with a single sealed result type that the Dart compiler forces you to exhaust — every switch you write is checked at compile time, so you can't forget the cancel case again.

Before With Uni Payments
API per gateway Different class, different callbacks, different error shape UniPayments.payWith*(...) everywhere
Cancellation Hidden inside catch (e) or a stringy status PaymentCancelled — a real type
Verification Dig through gateway-specific JSON result.transactionId + result.rawResponse
Native setup 6 different setup guides Documented per gateway here

Gateways

Gateway Region SDK Imperative call Native button
Razorpay India razorpay_flutter payWithRazorpay
Stripe Global flutter_stripe payWithStripe
PayPal Global braintree_flutter_plus payWithPaypal
Paystack Africa flutter_paystack_max payWithPaystack
Flutterwave Africa flutterwave_standard payWithFlutterwave
Paytm India paytmpayments_allinonesdk payWithPaytm
Cashfree India flutter_cashfree_pg_sdk payWithCashfree
PhonePe India · UPI phonepe_payment_sdk payWithPhonepe
Google Pay Android pay payWithGooglePay googlePayButton(...)
Apple Pay iOS pay payWithApplePay applePayButton(...)

Every imperative call returns Future<PaymentResult>. Wallet buttons exist because Google + Apple's brand guidelines require their own button design.


Install

dependencies:
  uni_payments: ^0.0.6
flutter pub add uni_payments
import 'package:uni_payments/uni_payments.dart';

Platform requirements

Platform Minimum
Flutter 3.32+ · Dart 3.8+ (sealed classes + switch expressions)
Android minSdkVersion 23 — modern Stripe / Razorpay / PhonePe builds need it
iOS iOS 15+ — driven by PhonePe's IntentSDK constraint

Required extra setup

PhonePe needs a private Maven repo on Android

PhonePe's IntentSDK is hosted on PhonePe's CloudRepo, not on Maven Central. Add the repository to your app's android/build.gradle.kts:

allprojects {
  repositories {
    google()
    mavenCentral()
    maven { url = uri("https://phonepe.mycloudrepo.io/public/repositories/phonepe-intentsdk-android") }
  }
}

Groovy DSL equivalent in android/build.gradle:

maven { url 'https://phonepe.mycloudrepo.io/public/repositories/phonepe-intentsdk-android' }

Core types

UniCustomer

Pass once, reuse everywhere a gateway prefills checkout fields.

const customer = UniCustomer(
  name: 'Ada Lovelace',
  email: 'ada@example.com',
  phone: '9999999999', // optional
);

PaymentResult

sealed class PaymentResult {
  String? gatewayName;       // 'razorpay', 'stripe', 'apple_pay', …
  String? message;
  Map<String, dynamic>? rawResponse;
}

final class PaymentSuccess   extends PaymentResult { String transactionId; }
final class PaymentFailure   extends PaymentResult { String errorCode; String message; }
final class PaymentCancelled extends PaymentResult { }

rawResponse holds the untouched gateway payload — useful for audit logs and webhook reconciliation.

UniPayments.isWalletSupported

Probe before rendering a wallet button — the underlying pay package silently no-ops on unsupported platforms.

final canApplePay = await UniPayments.isWalletSupported(
  WalletProvider.applePay,
  configJson,
);

Per-gateway cookbook

Razorpay
final result = await UniPayments.payWithRazorpay(
  keyId: 'YOUR_RAZORPAY_KEY_ID',
  amount: 25.00,                    // major units (₹25.00)
  businessName: 'Acme Inc',         // merchant header inside the sheet
  customer: customer,
  description: 'Pro subscription',
  themeColor: Colors.indigo,        // Color, not '#RRGGBB'
  currency: 'INR',
);
Stripe
final result = await UniPayments.payWithStripe(
  publishableKey: 'YOUR_STRIPE_PUBLISHABLE_KEY',
  clientSecret: 'YOUR_PAYMENT_INTENT_CLIENT_SECRET',     // from your server
  merchantDisplayName: 'Acme Inc',
  merchantCountryCode: 'US',

  // Optional — Apple Pay / Google Pay inside the PaymentSheet
  applePayMerchantId: 'merchant.com.acme.app',
  googlePayTestEnv: true,

  // Optional — saved cards (ephemeral key from your server)
  customerId: 'cus_xxx',
  customerEphemeralKeySecret: 'ek_test_xxx',
);
PayPal (Braintree drop-in)
final result = await UniPayments.payWithPaypal(
  tokenizationKey: 'YOUR_BRAINTREE_TOKENIZATION_KEY',
  amount: 25.00,
  customer: customer,
  currency: 'USD',
  countryCode: 'US',
  applePayMerchantId: 'merchant.com.acme', // optional
);
Paystack
final result = await UniPayments.payWithPaystack(
  context: context,
  secretKey: 'YOUR_PAYSTACK_SECRET_KEY',
  amount: 25.00,
  customer: customer,
  reference: 'ref_${DateTime.now().millisecondsSinceEpoch}',
  callbackUrl: 'https://acme.dev/paystack/callback',
  currency: UniPaystackCurrency.usd,
);
Flutterwave
final result = await UniPayments.payWithFlutterwave(
  context: context,
  publicKey: 'YOUR_FLUTTERWAVE_PUBLIC_KEY',
  currency: 'NGN',
  amount: 25.00,
  customer: customer,
  txRef: 'tx_${DateTime.now().millisecondsSinceEpoch}',
  redirectUrl: 'https://acme.dev/flutterwave/return',
  testMode: true,
);

Standard checkout only needs your public key. Encryption happens on Flutterwave's hosted modal.

Paytm
// txnToken is issued by your backend via Paytm's initiateTransaction API.
final result = await UniPayments.payWithPaytm(
  merchantId: 'YOUR_MERCHANT_ID',
  orderId: 'order_${DateTime.now().millisecondsSinceEpoch}',
  txnToken: 'YOUR_TXN_TOKEN',
  amount: 25.00,
  useStagingEnvironment: true,
);
Cashfree
// orderId + paymentSessionId come from your backend's call to the
// Cashfree Orders API.
final result = await UniPayments.payWithCashfree(
  orderId: 'order_${DateTime.now().millisecondsSinceEpoch}',
  paymentSessionId: 'session_xxx',
  useStagingEnvironment: true,
);
PhonePe
// requestBody is a base64-encoded JSON request your backend signs.
final result = await UniPayments.payWithPhonepe(
  merchantId: 'YOUR_MERCHANT_ID',
  flowId: 'flow_${DateTime.now().millisecondsSinceEpoch}',
  requestBody: '<base64-encoded JSON from your backend>',
  appSchema: 'unipaymentsdemo', // iOS URL scheme; '' on Android
  useStagingEnvironment: true,
);

Also requires the Maven repo described in Required extra setup.

Google Pay
// Imperative
final result = await UniPayments.payWithGooglePay(
  paymentConfigurationJson: configJson,
  lineItemLabel: 'Total',
  amount: 25.00,
);

// Native button
UniPayments.googlePayButton(
  paymentConfigurationJson: configJson,
  lineItemLabel: 'Total',
  amount: 25.00,
  buttonType: UniGooglePayButtonType.pay,
  onResult: (PaymentResult result) { /* … */ },
);
Apple Pay
final result = await UniPayments.payWithApplePay(
  paymentConfigurationJson: configJson,
  lineItemLabel: 'Total',
  amount: 25.00,
);

UniPayments.applePayButton(
  paymentConfigurationJson: configJson,
  lineItemLabel: 'Total',
  amount: 25.00,
  type: UniApplePayButtonType.buy,
  onResult: (PaymentResult result) { /* … */ },
);

Demo app

The repo ships a fully-styled demo with all ten gateways wired up — animated gradient background, glass-morphism tiles, error toasts, haptic feedback on each outcome.

git clone https://github.com/NehilKoshiya/uni_payments
cd uni_payments/example
flutter pub get
flutter run

Security notes

  • Never ship secret keys or clientSecrets in your binary — generate them on your server and pass them in.
  • Always verify PaymentSuccess server-side before fulfilling. Client-side success only means the SDK said so.
  • rawResponse carries the untouched gateway payload — log it for audit + webhook reconciliation.

Contributing

  • Bug or feature request — open an issue.
  • Sending a PR — flutter analyze must be clean and flutter test must pass.
  • Found this useful? A ⭐ on GitHub goes a long way.

Apache 2.0 · © Nehil Koshiya

Libraries

uni_payments
Uni Payments — a unified Flutter API over Razorpay, Stripe, Paystack, Flutterwave, Paytm, Google Pay, Apple Pay and PayPal/Braintree.