๐ŸŒŸ Flutter PayPal Payment Checkout V2

pub package License Flutter

A modern, safe, and powerful Flutter package for integrating PayPal Checkout using:

  • PayPal Orders API V2 (Recommended for all new apps)
  • Legacy PayPal Payments API V1 (For compatibility only)

Includes a full in-app WebView checkout, typed models, sandbox tools, and secure backend flows.


โค๏ธ Support the Project

If this package saved you development time, please consider supporting the work behind it:

PayPal Donation

๐Ÿ‘‰ https://paypal.me/mazenelgayar

InstaPay

๐Ÿ‘‰ https://ipn.eg/S/mazenel-gayarcib/instapay/0ecfXw Tag: mazenel-gayarcib@instapay

Your support directly motivates further updates, improvements, and new features. Thank you! โค๏ธ๐Ÿ™


๐Ÿš€ Features

  • ๐Ÿ”’ Production-safe PayPal Orders V2 support (create + capture)
  • ๐Ÿงพ Fully typed request/response models for V1 & V2 APIs
  • ๐ŸŒ Custom return/cancel URL schemes (paypal-sdk://success)
  • ๐Ÿงช Sandbox-friendly client-side payments
  • ๐ŸŽฏ Easy success / error / cancellation callbacks
  • ๐Ÿงฐ Integrated WebView + progress indicator
  • ๐Ÿ›  Backward compatible with PayPal Payments API V1
  • ๐Ÿ” Strong security protections against exposing client secrets

โš ๏ธ Security Warning

DO NOT PUT YOUR PAYPAL SECRET KEY IN A MOBILE APP IN PRODUCTION.

Flutter code can always be decompiled.

โœ” In production โ†’ always use backend-created orders โœ” In sandbox โ†’ it's safe to use local clientId + secretKey โœ” Never enable overrideInsecureClientCredentials in live mode


๐Ÿ“ฆ Installation

dependencies:
  flutter_paypal_payment_checkout_v2: ^2.1.0
flutter pub get

๐Ÿงญ Choosing an API Version

API Recommended? Notes
V2 (Orders API) โœ… Yes Modern, secure, officially recommended by PayPal
V1 (Payments API) โš ๏ธ Deprecated Older, but still supported for legacy apps

๐ŸŸฆ Example: PayPal Orders API V2: BACKEND FLOW PRODUCTION (Recommended)

void startPayPalFlow(BuildContext context, int servicePlanId) async {
  final service = PayPalService(DioHelper());

  // Open checkout view with backend-driven flow
  await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => PaypalCheckoutView<PaypalPaymentModel>(
        version: PayPalApiVersion.v2,
        sandboxMode: true,
        /// Pass a function that fetches the checkout URL and model from your backend
        getCheckoutUrl: () async {
          final result = await service.createOrder(servicePlanId: servicePlanId);
          return result; // Either<PayPalErrorModel, PaypalPaymentModel>
        },

        onUserPayment: (success, payment) async {
          print("Payment approved: ${payment.toJson()}");
          print("Capture data: ${success?.data}");

          // Capture via backend
          final captureResult = await service.captureOrder(orderId: payment.orderId!);
          captureResult.fold(
                (failure) => print("Capture failed: ${failure.message}"),
                (_) => print("Payment captured successfully"),
          );

          return Right<PayPalErrorModel, dynamic>(success?.data);
        },

        onError: (error) {
          print("Checkout error: ${error.message}");
          Navigator.pop(context);
        },

        onCancel: () {
          print("Payment cancelled by user");
          Navigator.pop(context);
        },
      ),
    ),
  );
}

๐ŸŸฆ Example: PayPal Orders API V2: Mobile Payment flow without backend

void _startV2Flow(BuildContext context) {
  final order = PayPalOrderRequestV2(
    intent: PayPalOrderIntentV2.capture,
    paymentSource: PayPalPaymentSourceV2(
      paymentMethodPreference:
          PayPalPaymentMethodPreferenceV2.immediatePaymentRequired,
      shippingPreference: PayPalShippingPreferenceV2.noShipping,
    ),
    purchaseUnits: [
      PayPalPurchaseUnitV2(
        amount: PayPalAmountV2(
          currency: 'USD',
          value: 100.0,
          itemTotal: 100.0,
          taxTotal: 0.0,
        ),
        items: [
          PaypalTransactionV2Item(
            name: 'Apple',
            description: 'Fresh apples',
            quantity: 2,
            unitAmount: 50.0,
            currency: 'USD',
            category: PayPalItemCategoryV2.physicalGoods,
          ),
        ],
      ),
    ],
  );

  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => PaypalCheckoutView(
        version: PayPalApiVersion.v2,
        sandboxMode: true,
        clientId: "SANDBOX_CLIENT_ID",
        secretKey: "SANDBOX_SECRET_KEY",
        getAccessToken: null,
        approvalUrl: null,
        payPalOrder: order,
        onUserPayment: (success, payment) async {
          print("Order Captured: ${success?.data}");
          return const Right<PayPalErrorModel, dynamic>(
            null,
          );
        },
        onError: (err) => print("Error: ${err.message}"),
        onCancel: () => print("Cancelled"),
      ),
    ),
  );
}

๐ŸŸก Example: PayPal Payments API V1 (Legacy)

void _startV1Flow(BuildContext context) {
  final tx = PaypalTransactionV1(
    amount: PaypalTransactionV1Amount(
      subTotal: 100,
      tax: 0,
      shipping: 0,
      handlingFee: 0,
      shippingDiscount: 0,
      insurance: 0,
      total: 100,
      currency: 'USD',
    ),
    description: "Payment for apples",
    items: [
      PaypalTransactionV1Item(
        name: "Apple",
        quantity: 4,
        price: 10,
        tax: 0,
        currency: "USD",
      ),
    ],
  );

  final order = PayPalOrderRequestV1(
    intent: PayPalOrderIntentV1.sale,
    transactions: [tx],
    noteToPayer: "Thank you for your purchase!",
  );

  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => PaypalCheckoutView(
        version: PayPalApiVersion.v1,
        sandboxMode: true,
        clientId: "SANDBOX_CLIENT_ID",
        secretKey: "SANDBOX_SECRET_KEY",
        getAccessToken: null,
        approvalUrl: null,
        payPalOrder: order,
        onUserPayment: (success, payment) async {
          print("Order Captured: ${success?.data}");
          return const Right<PayPalErrorModel, dynamic>(
            null,
          );
        },
        onError: (err) => print("Error: ${err.message}"),
        onCancel: () => print("Cancelled"),
      ),
    ),
  );
}

๐Ÿงช Sandbox-only Client-side Flow

โš ๏ธ Never use this in production.

PaypalCheckoutView(
  version: PayPalApiVersion.v2,
  sandboxMode: true,
  clientId: "SANDBOX_CLIENT_ID",
  secretKey: "SANDBOX_SECRET_KEY",
  overrideInsecureClientCredentials: true,
  payPalOrder: simpleV2Order,
  getAccessToken: null,
  approvalUrl: null,
  onUserPayment: (success, payment) => print(success?.data),
  onError: print,
  onCancel: () => print("Cancelled"),
);

๐Ÿ“š Documentation

This package includes strongly-typed models for:

โœ” PayPal Orders API V2

  • PayPalOrderRequestV2
  • PayPalPurchaseUnitV2
  • PayPalAmountV2
  • PayPalPaymentSourceV2
  • PayPalItemCategoryV2
  • PayPalCaptureOrderResponse

โœ” PayPal Payments API V1

  • PayPalOrderRequestV1
  • PaypalTransactionV1
  • PaypalTransactionV1Item
  • PayPalAllowedPaymentMethodV1

โœ” Core Models

  • PaypalPaymentModel
  • PayPalErrorModel
  • PayPalSuccessPaymentModel

๐Ÿ” Security Best Practices

Task Production Sandbox
Create Orders Backend Client or backend
Capture Orders Backend Client or backend
Use clientId / secretKey in app โŒ NEVER โœ” Allowed
Use return/cancel URLs Required Optional
Enable overrideInsecureClientCredentials โŒ NEVER โœ” Only for testing

๐Ÿ”ง Advanced Tips

Custom URL schemes

You may safely use:

paypal-sdk://success
paypal-sdk://cancel

Useful for mobile deep linking.


๐Ÿ“„ License

Libraries

flutter_paypal_payment_checkout_v2
Flutter PayPal Payment Checkout (V1 + V2)