๐ Flutter PayPal Payment Checkout V2
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
PayPalOrderRequestV2PayPalPurchaseUnitV2PayPalAmountV2PayPalPaymentSourceV2PayPalItemCategoryV2PayPalCaptureOrderResponse
โ PayPal Payments API V1
PayPalOrderRequestV1PaypalTransactionV1PaypalTransactionV1ItemPayPalAllowedPaymentMethodV1
โ Core Models
PaypalPaymentModelPayPalErrorModelPayPalSuccessPaymentModel
๐ 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
- MIT ยฉ 2025 Mazen El-Gayar
- MIT ยฉ 2023 Tharwat
Libraries
- flutter_paypal_payment_checkout_v2
- Flutter PayPal Payment Checkout (V1 + V2)