paypal_payment_flutter
Overview
A comprehensive Flutter plugin for integrating PayPal payments into your Flutter application. This plugin supports both Android and iOS platforms and provides access to PayPal's Orders API, Payments API (v1 & v2), Transactions API, and Checkout flows.
Features
- ✅ Orders API: Create, capture, and authorize orders
- ✅ Card Payments: Accept credit and debit card payments with 3D Secure support
- ✅ Payments API V1: Create, execute, capture, void, and refund payments
- ✅ Payments API V2: Capture, void, and refund authorized payments
- ✅ Transactions API: List and search transaction history
- ✅ Checkout API: Simplified checkout flows with native WebView integration
- ✅ Manual Flows: Build custom checkout experiences without using Checkout API
Installation
Add this to your package's pubspec.yaml file:
dependencies:
paypal_payment_flutter: ^0.0.3
Then run:
flutter pub get
Setup
1. Android Manifest Setup
To handle the redirect from the PayPal payment flow back to your app, you need to configure a custom URL scheme in your AndroidManifest.xml file. This allows your app to open automatically when the payment is complete.
- Open
android/app/src/main/AndroidManifest.xml. - Locate the
<activity>tag with theandroid:name=".MainActivity". - Add the following
<intent-filter>inside the<activity>tag:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="itheamc" android:host="paypal" />
</intent-filter>
2. Get PayPal Credentials
- Go to PayPal Developer Dashboard
- Create a new app or select an existing one
- Copy your Client ID and Secret
- Use Sandbox credentials for testing, Live credentials for production
3. Create .env File
Create a .env file in the root of your Flutter project:
PAYPAL_CLIENT_ID=your_client_id_here
PAYPAL_CLIENT_SECRET=your_client_secret_here
Example:
PAYPAL_CLIENT_ID=AX9HZkDVbFQDVyOQBO_t8oLscdNk9YTeXQ8lnKBoE0ns7Z_YTH70qPR0njSHge2QjqcNS3IkP3kxPB0b
PAYPAL_CLIENT_SECRET=EEpm782H_lewazmVlIlotj-3W8YNeEYXKFAmHYJk-4M9bsE1uC5Ptv8K6862Oc0MowwknHXg4_kb4bQX
Important: Add
.envto your.gitignorefile to keep your credentials secure:# .gitignore .env
4. Add .env to Assets
Update your pubspec.yaml to include the .env file as an asset:
flutter:
assets:
- .env
5. Initialize PayPal
Initialize the PayPal plugin in your app's main function or before using any PayPal APIs:
import 'package:paypal_payment_flutter/paypal_payment_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize PayPal
// By default it will be initialize with sandbox environment
await PaypalPayment.instance.initialize();
// For production, use:
// await PaypalPayment.instance.initialize(Environment.production);
runApp(MyApp());
}
The initialize() method automatically:
- Loads environment variables from the
.envfile - Retrieves
PAYPAL_CLIENT_IDandPAYPAL_CLIENT_SECRET - Configures the HTTP service with your credentials
- Initializes the native PayPal SDK on Android and iOS
Note: The plugin uses a custom environment variable loader that reads from the .env file. No additional packages like flutter_dotenv are required.
API Usage
Orders API
The Orders API allows you to create and manage orders for checkout flows.
Create an Order
import 'package:paypal_payment_flutter/paypal_payment_flutter.dart';
Future<void> createOrder() async {
await PaypalPayment.instance.orders.createOrder(
intent: OrderIntent.capture,
purchaseUnits: [
PurchaseUnit(
referenceId: 'default',
amount: Amount(
currencyCode: 'USD',
value: '100.00',
),
),
],
onInitiated: () {
print('Order creation Initiated');
},
onSuccess: (response) {
print('Order created: ${response.id}');
print('Status: ${response.status}');
// Extract approval URL for manual checkout
final approvalUrl = response.links
?.firstWhere((link) => link.rel == 'approve')
.href;
print('Approval URL: $approvalUrl');
},
onError: (error) {
print('Error creating order: $error');
},
);
}
Capture an Order
Future<void> captureOrder(String orderId) async {
await PaypalPayment.instance.orders.captureOrder(
orderId: orderId,
onInitiated: () {
print('Capture Initiated');
},
onSuccess: (response) {
print('Order captured: ${response.id}');
print('Status: ${response.status}');
},
onError: (error) {
print('Error capturing order: $error');
},
);
}
Authorize an Order
Future<void> authorizeOrder(String orderId) async {
await PaypalPayment.instance.orders.authorizeOrder(
orderId: orderId,
onInitiated: () {
print('Authorization Initiated');
},
onSuccess: (response) {
print('Order authorized: ${response.id}');
},
onError: (error) {
print('Error authorizing order: $error');
},
);
}
Payments API V1
The Payments API V1 provides comprehensive payment management capabilities.
Create a Payment
Future<void> createPayment() async {
await PaypalPayment.instance.payments.v1.createPayment(
intent: PaymentIntent.sale,
payer: Payer(paymentMethod: "paypal"),
transactions: [
Transaction(
amount: TransactionAmount(
total: "10.00",
currency: "USD",
),
description: "Payment for services",
),
],
redirectUrls: RedirectUrls(
returnUrl: "https://yourapp.com/return",
cancelUrl: "https://yourapp.com/cancel",
),
onInitiated: () {
print('Create Payment Initiated');
},
onSuccess: (response) {
print('Payment created: ${response.id}');
// Get approval URL
final approvalUrl = response.links
?.firstWhere((link) => link.rel == 'approval_url')
.href;
print('Approval URL: $approvalUrl');
},
onError: (error) {
print('Error: $error');
},
);
}
Execute a Payment
After user approves the payment, execute it:
Future<void> executePayment(String paymentId, String payerId) async {
await PaypalPayment.instance.payments.v1.executePayment(
paymentId: paymentId,
payerId: payerId,
onInitiated: () {
print('Executed Initiated');
},
onSuccess: (response) {
print('Payment executed: ${response.id}');
print('State: ${response.state}');
},
onError: (error) {
print('Error: $error');
},
);
}
Capture Authorized Payment
Future<void> captureAuthorizedPayment(String authorizationId) async {
await PaypalPayment.instance.payments.v1.captureAuthorizedPayment(
authorizationId: authorizationId,
amount: TransactionAmount(
total: "10.00",
currency: "USD",
),
onInitiated: () {
print('Capture initiated');
},
onSuccess: (response) {
print('Payment captured: ${response.id}');
},
onError: (error) {
print('Error: $error');
},
);
}
Void Authorized Payment
Future<void> voidAuthorizedPayment(String authorizationId) async {
await PaypalPayment.instance.payments.v1.voidAuthorizedPayment(
authorizationId: authorizationId,
onInitiated: () {
print('Void Initiated');
},
onSuccess: (response) {
print('Authorization voided: ${response.id}');
},
onError: (error) {
print('Error: $error');
},
);
}
Refund a Payment
Future<void> refundCapturedPayment(String captureId) async {
await PaypalPayment.instance.payments.v1.refundCapturedPayment(
captureId: captureId,
amount: TransactionAmount(
total: "5.00",
currency: "USD",
),
onInitiated: () {
print('Refund Initiated');
},
onSuccess: (response) {
print('Refund processed: ${response.id}');
},
onError: (error) {
print('Error: $error');
},
);
}
Get Payment Details
Future<void> getPaymentDetails(String paymentId) async {
await PaypalPayment.instance.payments.v1.getPaymentDetails(
paymentId: paymentId,
onInitiated: () {
print('Payment Details Request Initiated');
},
onSuccess: (response) {
print('Payment ID: ${response.id}');
print('State: ${response.state}');
},
onError: (error) {
print('Error: $error');
},
);
}
Payments API V2
The Payments API V2 provides modern payment authorization and capture flows.
Capture Authorized Payment (V2)
Future<void> captureAuthorizedPaymentV2(String authorizationId) async {
await PaypalPayment.instance.payments.v2.captureAuthorizedPayment(
authorizationId: authorizationId,
amount: Amount(
currencyCode: "USD",
value: "10.00",
),
onInitiated: () {
print('Capture Authorization Initiated');
},
onSuccess: (response) {
print('Payment captured: ${response.id}');
print('Status: ${response.status}');
},
onError: (error) {
print('Error: $error');
},
);
}
Void Authorized Payment (V2)
Future<void> voidAuthorizedPaymentV2(String authorizationId) async {
await PaypalPayment.instance.payments.v2.voidAuthorizedPayment(
authorizationId: authorizationId,
onInitiated: () {
print('Void Authorization Initiated');
},
onSuccess: (response) {
print('Authorization voided: ${response.id}');
},
onError: (error) {
print('Error: $error');
},
);
}
Refund Captured Payment (V2)
Future<void> refundCapturedPaymentV2(String captureId) async {
await PaypalPayment.instance.payments.v2.refundCapturedPayment(
captureId: captureId,
amount: Amount(
currencyCode: "USD",
value: "5.00",
),
onInitiated: () {
print('Refund Capture Payment Initiated');
},
onSuccess: (response) {
print('Refund processed: ${response.id}');
},
onError: (error) {
print('Error: $error');
},
);
}
Transactions API
List and search transaction history.
Future<void> listTransactions() async {
final startDate = DateTime.now().subtract(Duration(days: 30));
final endDate = DateTime.now();
await PaypalPayment.instance.transactions.listTransactions(
startDate: startDate,
endDate: endDate,
onInitiated: () {
print('Loading Initiated');
},
onSuccess: (response) {
print('Total transactions: ${response.transactionDetails?.length}');
response.transactionDetails?.forEach((transaction) {
print('Transaction ID: ${transaction.transactionInfo?.transactionId}');
print('Amount: ${transaction.transactionInfo?.transactionAmount?.value}');
});
},
onError: (error) {
print('Error: $error');
},
);
}
Card Payment API
The Card Payment API allows you to accept credit and debit card payments directly. It supports 3D Secure authentication and handles the complete payment flow.
⚠️ Security Notice Card details are highly sensitive. Never log, store, or cache card information. Always handle card data according to PCI-DSS guidelines.
Card Payment Models
CardDetail: Contains the card information required for payment.
import 'package:paypal_payment_flutter/paypal_payment_flutter.dart';
final cardDetail = CardDetail(
number: '4111111111111111', // Card number (13-19 digits)
expirationMonth: '12', // Expiration month (MM format)
expirationYear: '2025', // Expiration year (YYYY format)
securityCode: '123', // CVV/CVC (3-4 digits)
cardholderName: 'John Doe', // Name on card
billingAddress: BillingAddress(
countryCode: 'US', // Required: ISO 3166-1 alpha-2 code
streetAddress: '123 Main Street', // Optional: Street address
extendedAddress: 'Apt 4B', // Optional: Apartment/Suite
locality: 'San Francisco', // Optional: City
region: 'CA', // Optional: State/Province
postalCode: '94105', // Optional: ZIP/Postal code
),
);
SCA (Strong Customer Authentication): Controls when 3D Secure authentication is performed.
enum SCA {
whenRequired, // Default: Only when mandated by regulation
always, // Always perform 3D Secure
}
Checkout with Card Payment
The simplest way to accept card payments is using the checkoutOrderWithCard method, which handles the entire flow:
Future<void> checkoutWithCard() async {
// Define card details
final cardDetail = CardDetail(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2025',
securityCode: '123',
cardholderName: 'John Doe',
billingAddress: BillingAddress(
countryCode: 'US',
streetAddress: '123 Main Street',
locality: 'San Francisco',
region: 'CA',
postalCode: '94105',
),
);
// Process card payment
await PaypalPayment.instance.checkout.checkoutOrderWithCard(
intent: OrderIntent.capture,
purchaseUnits: [
PurchaseUnit(
referenceId: 'default',
amount: Amount(
currencyCode: 'USD',
value: '100.00',
),
),
],
cardDetail: cardDetail,
sca: SCA.whenRequired, // Optional: defaults to whenRequired
onInitiated: () {
print('Card payment initiated');
},
onSuccess: (orderId) {
print('Payment successful! Order ID: $orderId');
// Order is automatically captured
},
onError: (error) {
print('Payment failed: $error');
},
);
}
Manual Card Payment Flow
For more control, you can manually handle each step:
import 'package:paypal_payment_flutter/paypal_payment_flutter.dart';
class ManualCardPayment extends StatefulWidget {
@override
_ManualCardPaymentState createState() => _ManualCardPaymentState();
}
class _ManualCardPaymentState extends State<ManualCardPayment> {
String? orderId;
// Step 1: Create the order
Future<void> createOrder() async {
await PaypalPayment.instance.orders.createOrder(
intent: OrderIntent.capture,
purchaseUnits: [
PurchaseUnit(
referenceId: 'default',
amount: Amount(
currencyCode: 'USD',
value: '100.00',
),
),
],
onSuccess: (response) {
setState(() {
orderId = response.id;
});
// Step 2: Initiate card payment
if (orderId != null) {
initiateCardPayment(orderId!);
}
},
onError: (error) {
print('Order creation failed: $error');
},
);
}
// Step 2: Initiate card payment request
Future<void> initiateCardPayment(String orderId) async {
final cardDetail = CardDetail(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2025',
securityCode: '123',
cardholderName: 'John Doe',
billingAddress: BillingAddress(countryCode: 'US'),
);
await PaypalPayment.instance.orders.initiateCardPaymentRequest(
orderId: orderId,
cardDetail: cardDetail,
sca: SCA.whenRequired,
onSuccess: (orderId, status, didAttemptThreeDSecure) {
print('Card payment approved');
print('3D Secure attempted: $didAttemptThreeDSecure');
// Step 3: Capture the order
if (orderId != null) {
captureOrder(orderId);
}
},
onFailure: (orderId, reason, code, correlationId) {
print('Payment failed: $reason (Code: $code)');
print('Correlation ID: $correlationId');
},
onCancel: (orderId) {
print('Payment canceled by user');
},
onError: (error) {
print('Error: $error');
},
);
}
// Step 3: Capture the order
Future<void> captureOrder(String orderId) async {
await PaypalPayment.instance.orders.captureOrder(
orderId: orderId,
onSuccess: (response) {
print('Order captured successfully!');
print('Capture ID: ${response.id}');
print('Status: ${response.status}');
},
onError: (error) {
print('Capture failed: $error');
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Card Payment')),
body: Center(
child: ElevatedButton(
onPressed: createOrder,
child: Text('Pay with Card'),
),
),
);
}
}
Test Card Numbers
Use these test cards in the PayPal Sandbox environment:
| Card Type | Number | CVV | Expiry |
|---|---|---|---|
| Visa | 4111111111111111 | 123 | Any future |
| Visa | 4032039594896467 | 123 | Any future |
| MasterCard | 5555555555554444 | 123 | Any future |
| MasterCard | 2223000048410010 | 123 | Any future |
| Amex | 378282246310005 | 1234 | Any future |
| Discover | 6011111111111117 | 123 | Any future |
Note: For production, use real card details. Test cards only work in Sandbox mode.
Checkout API
The Checkout API provides simplified payment flows with built-in WebView integration.
Checkout with Order
Future<void> checkoutWithOrder(BuildContext context) async {
await PaypalPayment.instance.checkout.checkoutOrder(
intent: OrderIntent.capture,
purchaseUnits: [
PurchaseUnit(
referenceId: 'default',
amount: Amount(
currencyCode: 'USD',
value: '100.00',
),
),
],
onInitiated: () {
print('Order Checkout Process Initiated');
},
onSuccess: (orderId) {
print('Order completed: $orderId');
// Order is automatically captured
},
onError: (error) {
print('Checkout error: $error');
},
);
}
Checkout with Payment (V1)
Future<void> checkoutWithPayment(BuildContext context) async {
await PaypalPayment.instance.checkout.checkoutPayment(
context,
intent: PaymentIntent.sale,
payer: Payer(paymentMethod: "paypal"),
transactions: [
Transaction(
amount: TransactionAmount(
total: "10.00",
currency: "USD",
),
description: "Payment for services",
),
],
redirectUrls: RedirectUrls(
returnUrl: "https://yourapp.com/return",
cancelUrl: "https://yourapp.com/cancel",
),
onInitiated: () {
print('Payment Checkout Process Initiated');
},
onSuccess: (paymentId) {
print('Payment completed: $paymentId');
// Payment is automatically executed
},
onError: (error) {
print('Checkout error: $error');
},
);
}
Manual Checkout Flow (Without Checkout API)
If you prefer to build a custom checkout experience without using the Checkout API, you can manually handle the flow:
Option 1: Manual Order Flow
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:paypal_payment_flutter/paypal_payment_flutter.dart';
class ManualOrderCheckout extends StatefulWidget {
@override
_ManualOrderCheckoutState createState() => _ManualOrderCheckoutState();
}
class _ManualOrderCheckoutState extends State<ManualOrderCheckout> {
String? orderId;
String? approvalUrl;
// Step 1: Create the order
Future<void> createOrder() async {
await PaypalPayment.instance.orders.createOrder(
intent: OrderIntent.capture,
purchaseUnits: [
PurchaseUnit(
referenceId: 'default',
amount: Amount(
currencyCode: 'USD',
value: '100.00',
),
),
],
onSuccess: (response) {
setState(() {
orderId = response.id;
// Extract approval URL
approvalUrl = response.links
?.firstWhere((link) => link.rel == 'approve')
.href;
});
// Step 2: Open approval URL in your custom WebView or browser
if (approvalUrl != null) {
openApprovalUrl(approvalUrl!);
}
},
onError: (error) {
print('Error: $error');
},
);
}
// Step 2: Open approval URL
void openApprovalUrl(String url) {
// Option A: Use WebView
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomWebView(
url: url,
onApproved: () {
// Step 3: Capture the order after approval
captureOrder();
},
),
),
);
// Option B: Use external browser
// launchUrl(Uri.parse(url));
}
// Step 3: Capture the order
Future<void> captureOrder() async {
if (orderId == null) return;
await PaypalPayment.instance.orders.captureOrder(
orderId: orderId!,
onSuccess: (response) {
print('Order captured successfully!');
print('Capture ID: ${response.id}');
// Handle success
},
onError: (error) {
print('Capture error: $error');
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Manual Checkout')),
body: Center(
child: ElevatedButton(
onPressed: createOrder,
child: Text('Pay with PayPal'),
),
),
);
}
}
// Custom WebView widget
class CustomWebView extends StatelessWidget {
final String url;
final VoidCallback onApproved;
const CustomWebView({
required this.url,
required this.onApproved,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('PayPal Checkout')),
body: WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onUrlChange: (change) {
// Monitor URL changes to detect approval
if (change.url?.contains('yourapp.com/return') ?? false) {
Navigator.pop(context);
onApproved();
}
},
),
)
..loadRequest(Uri.parse(url)),
),
);
}
}
Option 2: Manual Payment Flow (V1)
class ManualPaymentCheckout extends StatefulWidget {
@override
_ManualPaymentCheckoutState createState() => _ManualPaymentCheckoutState();
}
class _ManualPaymentCheckoutState extends State<ManualPaymentCheckout> {
String? paymentId;
String? approvalUrl;
// Step 1: Create payment
Future<void> createPayment() async {
await PaypalPayment.instance.payments.v1.createPayment(
intent: PaymentIntent.sale,
payer: Payer(paymentMethod: "paypal"),
transactions: [
Transaction(
amount: TransactionAmount(
total: "10.00",
currency: "USD",
),
),
],
redirectUrls: RedirectUrls(
returnUrl: "https://yourapp.com/return",
cancelUrl: "https://yourapp.com/cancel",
),
onSuccess: (response) {
setState(() {
paymentId = response.id;
approvalUrl = response.links
?.firstWhere((link) => link.rel == 'approval_url')
.href;
});
if (approvalUrl != null) {
openApprovalUrl(approvalUrl!);
}
},
onError: (error) {
print('Error: $error');
},
);
}
// Step 2: Open approval URL (similar to above)
void openApprovalUrl(String url) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomWebView(
url: url,
onApproved: (Uri returnUri) {
// Extract PayerID from return URL
final payerId = returnUri.queryParameters['PayerID'];
if (payerId != null) {
executePayment(payerId);
}
},
),
),
);
}
// Step 3: Execute payment
Future<void> executePayment(String payerId) async {
if (paymentId == null) return;
await PaypalPayment.instance.payments.v1.executePayment(
paymentId: paymentId!,
payerId: payerId,
onSuccess: (response) {
print('Payment executed successfully!');
// Handle success
},
onError: (error) {
print('Execution error: $error');
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Manual Payment')),
body: Center(
child: ElevatedButton(
onPressed: createPayment,
child: Text('Pay with PayPal'),
),
),
);
}
}
Complete Example
See the example directory for a complete working application demonstrating all APIs.
Platform Support
| Platform | Support |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Web | ❌ |
| macOS | ❌ |
| Windows | ❌ |
| Linux | ❌ |
Requirements
- Flutter SDK: >=3.3.0
- Dart SDK: ^3.10.0
- Android: minSdkVersion 21
- iOS: 14.0+ (Required by the latest Paypal iOS SDK)
Testing
The plugin includes comprehensive unit and widget tests. Run tests with:
flutter test
Additional Resources
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues, questions, or contributions, please visit the GitHub repository.