paypal_checkout_flutter 0.0.1
paypal_checkout_flutter: ^0.0.1 copied to clipboard
A Flutter package for PayPal payments using Pigeon for type-safe Dart/Kotlin communication.
paypal_checkout_flutter #
Paquete Flutter para integrar pagos con PayPal usando el PayPal Mobile SDK v2.3.0 nativo de Android. Comunicación type-safe entre Dart y Kotlin vía Pigeon.
No usa WebView. Abre el navegador del sistema o procesa tarjetas directamente con el SDK nativo.
Características #
| Funcionalidad | Método | Backend requerido |
|---|---|---|
| Checkout PayPal | pay() |
Sí (crea la orden) |
| Checkout PayPal sin backend | payDirect() |
No |
| Pay Later (financiación) | pay() + payLater |
Sí (crea la orden) |
| Pago con tarjeta | payWithCard() |
Sí (crea la orden) |
| Pago con tarjeta sin backend | payWithCardDirect() |
No |
| Guardar cuenta PayPal (Vault) | vaultPaypal() |
Sí (crea setup token) |
| Guardar tarjeta (Vault) | vaultCard() |
Sí (crea setup token) |
| Vault PayPal sin backend | vaultPaypalDirect() |
No |
| Vault tarjeta sin backend | vaultCardDirect() |
No |
| Consultar orden | getOrderDetails() |
No |
| Reembolso | refund() |
No |
- Soporte completo de 3D Secure en pagos con tarjeta
- Arquitectura limpia: entidades, repositorios, mappers
Either<Failure, Success>con dartz para manejo de errores
Requisitos #
- Android:
minSdk 23,compileSdk 34, Java 17 - Flutter:
>=1.17.0 - Una app de PayPal (developer.paypal.com)
Instalación #
dependencies:
paypal_checkout_flutter:
git:
url: https://github.com/TU_USUARIO/paypal_checkout_flutter.git
Configuración Android #
En tu AndroidManifest.xml, agrega el intent filter para el deep link de retorno:
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<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="com.example.myapp" android:host="paypalpay" />
</intent-filter>
</activity>
Uso #
1. Inicializar (una sola vez en main) #
Inicializa una sola vez al arrancar la app. Después usa la instancia desde cualquier pantalla.
Opción A: Variable global (simple)
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:paypal_checkout_flutter/paypal_checkout_flutter.dart';
final paypal = FlutterPaypalPayment();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await paypal.init(PaypalConfig(
clientId: 'TU_CLIENT_ID',
environment: PaypalEnvironment.sandbox,
returnUrl: 'com.example.myapp://paypalpay',
));
runApp(MyApp());
}
// En cualquier pantalla
import '../main.dart'; // o donde declaraste `paypal`
final result = await paypal.payDirect(
clientSecret: 'TU_SECRET',
params: PaymentParams(amount: '25.00', currencyCode: 'USD'),
);
Opción B: GetIt (inyección de dependencias)
// lib/injection.dart
import 'package:get_it/get_it.dart';
import 'package:paypal_checkout_flutter/paypal_checkout_flutter.dart';
final getIt = GetIt.instance;
Future<void> configureDependencies() async {
final paypal = FlutterPaypalPayment();
await paypal.init(PaypalConfig(
clientId: 'TU_CLIENT_ID',
environment: PaypalEnvironment.sandbox,
returnUrl: 'com.example.myapp://paypalpay',
));
getIt.registerSingleton<FlutterPaypalPayment>(paypal);
}
// lib/main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await configureDependencies();
runApp(MyApp());
}
// En cualquier pantalla o servicio
final paypal = getIt<FlutterPaypalPayment>();
final result = await paypal.pay(PaymentRequest(orderId: 'ORDER_ID'));
Opción C: Riverpod
// lib/providers/paypal_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paypal_checkout_flutter/paypal_checkout_flutter.dart';
final paypalProvider = Provider<FlutterPaypalPayment>((ref) {
throw UnimplementedError('Se inicializa en main');
});
// lib/main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final paypal = FlutterPaypalPayment();
await paypal.init(PaypalConfig(
clientId: 'TU_CLIENT_ID',
environment: PaypalEnvironment.sandbox,
returnUrl: 'com.example.myapp://paypalpay',
));
runApp(
ProviderScope(
overrides: [paypalProvider.overrideWithValue(paypal)],
child: MyApp(),
),
);
}
// En cualquier widget
class PayScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () async {
final paypal = ref.read(paypalProvider);
final result = await paypal.pay(
PaymentRequest(orderId: 'ORDER_ID'),
);
result.fold(
(f) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${f.message}')),
),
(s) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Pagado: ${s.orderId}')),
),
);
},
child: Text('Pagar'),
);
}
}
Nota:
init()configura el SDK nativo una vez. Después solo llamas los métodos de pago/vault directamente.
2. Checkout PayPal (con backend) #
Tu servidor crea la orden con la API de PayPal y te devuelve el orderId.
final result = await paypal.pay(
PaymentRequest(orderId: 'ORDER_ID_DEL_BACKEND'),
);
result.fold(
(failure) => print('Error: ${failure.message} (${failure.code})'),
(success) => print('Pagado! Orden: ${success.orderId}, Payer: ${success.payerId}'),
);
3. Checkout PayPal (sin backend) #
Crea la orden, abre el checkout y captura el pago — todo desde Flutter.
Nota: Requiere tu
clientSecret. No se recomienda en producción.
final result = await paypal.payDirect(
clientSecret: 'TU_CLIENT_SECRET',
params: PaymentParams(
amount: '25.00',
currencyCode: 'USD',
description: 'Compra de producto X',
),
);
result.fold(
(failure) => print('Error: ${failure.message}'),
(success) => print('Pagado y capturado! Orden: ${success.orderId}'),
);
Parámetros opcionales de PaymentParams:
| Parámetro | Descripción |
|---|---|
description |
Descripción mostrada al comprador |
customId |
Tu referencia interna |
invoiceId |
Número de factura |
softDescriptor |
Texto en el estado de cuenta (máx 22 chars) |
4. Pago con tarjeta (con backend) #
Cobra directamente una tarjeta sin que el usuario inicie sesión en PayPal. Soporta autenticación 3D Secure automáticamente.
final result = await paypal.payWithCard(
CardPaymentRequest(
orderId: 'ORDER_ID_DEL_BACKEND',
card: PaymentCard(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
),
// sca: 'SCA_ALWAYS', // Forzar 3DS (por defecto: SCA_WHEN_REQUIRED)
),
);
result.fold(
(failure) => print('Error: ${failure.message} (${failure.code})'),
(success) => print('Pagado con tarjeta! Orden: ${success.orderId}, '
'Status: ${success.status}, 3DS: ${success.didAttemptThreeDSecure}'),
);
5. Pago con tarjeta (sin backend) #
final result = await paypal.payWithCardDirect(
clientSecret: 'TU_CLIENT_SECRET',
params: PaymentParams(
amount: '50.00',
currencyCode: 'USD',
description: 'Compra con tarjeta',
),
buildRequest: (orderId) => CardPaymentRequest(
orderId: orderId,
card: PaymentCard(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
),
),
);
result.fold(
(failure) => print('Error: ${failure.message}'),
(success) => print('Pagado y capturado! Orden: ${success.orderId}'),
);
6. Vault: Guardar cuenta PayPal #
Guarda un método de pago PayPal para cobros futuros. Necesitas crear un setup token desde tu servidor usando la API de Setup Tokens.
final result = await paypal.vaultPaypal(
VaultPaypalRequest(setupTokenId: 'SETUP_TOKEN_DEL_BACKEND'),
);
result.fold(
(failure) => print('Vault error: ${failure.message}'),
(success) => print('PayPal guardado! Token: ${success.setupTokenId}, '
'Status: ${success.status}'),
);
7. Vault: Guardar tarjeta #
Guarda una tarjeta para cobros futuros, con soporte de 3D Secure.
final result = await paypal.vaultCard(
VaultCardRequest(
setupTokenId: 'SETUP_TOKEN_DEL_BACKEND',
card: PaymentCard(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
),
),
);
result.fold(
(failure) => print('Card vault error: ${failure.message}'),
(success) => print('Tarjeta guardada! Token: ${success.setupTokenId}'),
);
8. Pay Later (financiación PayPal) #
Ofrece PayPal Pay Later como opción de financiación. El usuario puede pagar en cuotas.
final result = await paypal.pay(
PaymentRequest(
orderId: 'ORDER_ID_DEL_BACKEND',
fundingSource: PaypalFundingSource.payLater,
),
);
result.fold(
(failure) => print('Pay Later error: ${failure.message}'),
(success) => print('Pay Later completado! Orden: ${success.orderId}'),
);
9. Vault PayPal sin backend #
Crea el setup token, guarda la cuenta PayPal y crea el payment token — todo desde Flutter.
Nota: Requiere tu
clientSecret. No se recomienda en producción.
final result = await paypal.vaultPaypalDirect(
clientSecret: 'TU_CLIENT_SECRET',
customer: {'id': 'CUSTOMER_123'},
);
result.fold(
(failure) => print('Vault error: ${failure.message}'),
(success) => print('PayPal guardado! Payment Token: $success'),
);
10. Vault tarjeta sin backend #
final result = await paypal.vaultCardDirect(
clientSecret: 'TU_CLIENT_SECRET',
card: PaymentCard(
number: '4111111111111111',
expirationMonth: '12',
expirationYear: '2028',
securityCode: '123',
),
customer: {'id': 'CUSTOMER_123'},
);
result.fold(
(failure) => print('Card vault error: ${failure.message}'),
(success) => print('Tarjeta guardada! Payment Token: $success'),
);
11. Consultar detalles de una orden #
Obtén el estado y detalles de una orden creada previamente.
final result = await paypal.getOrderDetails(
clientSecret: 'TU_CLIENT_SECRET',
orderId: 'ORDER_ID',
);
result.fold(
(failure) => print('Error: ${failure.message}'),
(order) => print('Estado: ${order['status']}, '
'Monto: ${order['purchase_units']?[0]?['amount']}'),
);
12. Reembolso (total o parcial) #
Reembolsa un pago capturado. Si no especificas monto, se reembolsa el total.
// Reembolso total
final result = await paypal.refund(
clientSecret: 'TU_CLIENT_SECRET',
captureId: 'CAPTURE_ID',
);
// Reembolso parcial
final partial = await paypal.refund(
clientSecret: 'TU_CLIENT_SECRET',
captureId: 'CAPTURE_ID',
amount: '5.00',
currencyCode: 'USD',
);
Arquitectura #
lib/
├── paypal_checkout_flutter.dart # Exports públicos
└── src/
├── flutter_paypal_payment_plugin.dart # API pública (FlutterPaypalPayment)
├── domain/
│ ├── entities/ # PaypalConfig, PaymentRequest, PaymentCard, etc.
│ └── repositories/ # Contratos abstractos
├── data/
│ ├── repositories/ # Implementación delegando a Pigeon
│ ├── mappers/ # Dart ↔ Pigeon message mappers
│ └── services/ # PaypalOrderService (REST API directa)
└── generated/ # Código auto-generado por Pigeon
android/src/main/kotlin/
└── FlutterPaypalPaymentPlugin.kt # Implementación nativa (PayPal SDK)
Flujo de datos #
Flutter (usuario)
↓
FlutterPaypalPayment ← API pública
↓
PaypalRepository ← Contrato
↓
PaypalRepositoryImpl ← Mappers Dart → Pigeon
↓
PaypalHostApi (Pigeon) ← Comunicación type-safe
↓
FlutterPaypalPaymentPlugin.kt ← PayPal SDK nativo (Android)
↓
PayPal (browser/3DS/SDK)
↓ deep link
onNewIntent → finishStart/finishApproveOrder/finishVault
↓
Callback → Pigeon → Dart → Either<Failure, Success>
Licencia #
MIT