332 lines
10 KiB
Dart
332 lines
10 KiB
Dart
library flutter_paypal;
|
|
|
|
import 'dart:core';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_paypal/src/screens/complete_payment.dart';
|
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
// Import for Android features.
|
|
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
|
// Import for iOS features.
|
|
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
|
|
|
import 'src/PaypalServices.dart';
|
|
import 'src/errors/network_error.dart';
|
|
|
|
class UsePaypal extends StatefulWidget {
|
|
final Function onSuccess, onCancel, onError;
|
|
final String returnURL, cancelURL, note, clientId, secretKey;
|
|
final List transactions;
|
|
final bool sandboxMode;
|
|
const UsePaypal({
|
|
Key? key,
|
|
required this.onSuccess,
|
|
required this.onError,
|
|
required this.onCancel,
|
|
required this.returnURL,
|
|
required this.cancelURL,
|
|
required this.transactions,
|
|
required this.clientId,
|
|
required this.secretKey,
|
|
this.sandboxMode = false,
|
|
this.note = '',
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<StatefulWidget> createState() {
|
|
return UsePaypalState();
|
|
}
|
|
}
|
|
|
|
class UsePaypalState extends State<UsePaypal> {
|
|
late final WebViewController _controller;
|
|
String checkoutUrl = '';
|
|
String navUrl = '';
|
|
String executeUrl = '';
|
|
String accessToken = '';
|
|
bool loading = true;
|
|
bool pageLoading = true;
|
|
bool loadingError = false;
|
|
late PaypalServices services;
|
|
int pressed = 0;
|
|
|
|
Map getOrderParams() {
|
|
Map<String, dynamic> temp = {
|
|
"intent": "sale",
|
|
"payer": {"payment_method": "paypal"},
|
|
"transactions": widget.transactions,
|
|
"note_to_payer": widget.note,
|
|
"redirect_urls": {
|
|
"return_url": widget.returnURL,
|
|
"cancel_url": widget.cancelURL
|
|
}
|
|
};
|
|
return temp;
|
|
}
|
|
|
|
loadPayment() async {
|
|
setState(() {
|
|
loading = true;
|
|
});
|
|
try {
|
|
Map getToken = await services.getAccessToken();
|
|
if (getToken['token'] != null) {
|
|
accessToken = getToken['token'];
|
|
final transactions = getOrderParams();
|
|
final res =
|
|
await services.createPaypalPayment(transactions, accessToken);
|
|
if (res["approvalUrl"] != null) {
|
|
setState(() {
|
|
checkoutUrl = res["approvalUrl"].toString();
|
|
navUrl = res["approvalUrl"].toString();
|
|
executeUrl = res["executeUrl"].toString();
|
|
loading = false;
|
|
pageLoading = false;
|
|
loadingError = false;
|
|
});
|
|
_controller.loadRequest(Uri.parse(checkoutUrl));
|
|
} else {
|
|
widget.onError(res);
|
|
setState(() {
|
|
loading = false;
|
|
pageLoading = false;
|
|
loadingError = true;
|
|
});
|
|
}
|
|
} else {
|
|
widget.onError("${getToken['message']}");
|
|
|
|
setState(() {
|
|
loading = false;
|
|
pageLoading = false;
|
|
loadingError = true;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
widget.onError(e);
|
|
setState(() {
|
|
loading = false;
|
|
pageLoading = false;
|
|
loadingError = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
services = PaypalServices(
|
|
sandboxMode: widget.sandboxMode,
|
|
clientId: widget.clientId,
|
|
secretKey: widget.secretKey,
|
|
);
|
|
setState(() {
|
|
navUrl = widget.sandboxMode
|
|
? 'https://api.sandbox.paypal.com'
|
|
: 'https://www.api.paypal.com';
|
|
});
|
|
// Enable hybrid composition.
|
|
loadPayment();
|
|
|
|
// #docregion platform_features
|
|
late final PlatformWebViewControllerCreationParams params;
|
|
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
|
params = WebKitWebViewControllerCreationParams(
|
|
allowsInlineMediaPlayback: true,
|
|
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
|
|
);
|
|
} else {
|
|
params = const PlatformWebViewControllerCreationParams();
|
|
}
|
|
|
|
final WebViewController controller =
|
|
WebViewController.fromPlatformCreationParams(params);
|
|
// #enddocregion platform_features
|
|
|
|
controller
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setBackgroundColor(const Color(0x00000000))
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onProgress: (int progress) {
|
|
debugPrint('WebView is loading (progress : $progress%)');
|
|
},
|
|
onPageStarted: (String url) {
|
|
setState(() {
|
|
pageLoading = true;
|
|
loadingError = false;
|
|
});
|
|
debugPrint('Page started loading: $url');
|
|
},
|
|
onPageFinished: (String url) {
|
|
setState(() {
|
|
navUrl = url;
|
|
pageLoading = false;
|
|
});
|
|
},
|
|
onWebResourceError: (WebResourceError error) {
|
|
debugPrint('''
|
|
Page resource error:
|
|
code: ${error.errorCode}
|
|
description: ${error.description}
|
|
errorType: ${error.errorType}
|
|
isForMainFrame: ${error.isForMainFrame}
|
|
''');
|
|
},
|
|
onNavigationRequest: (NavigationRequest request) async {
|
|
if (request.url.startsWith('https://www.youtube.com/')) {
|
|
debugPrint('blocking navigation to ${request.url}');
|
|
return NavigationDecision.prevent;
|
|
}
|
|
if (request.url.contains(widget.returnURL)) {
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => CompletePayment(
|
|
url: request.url,
|
|
services: services,
|
|
executeUrl: executeUrl,
|
|
accessToken: accessToken,
|
|
onSuccess: widget.onSuccess,
|
|
onCancel: widget.onCancel,
|
|
onError: widget.onError)),
|
|
);
|
|
}
|
|
if (request.url.contains(widget.cancelURL)) {
|
|
final uri = Uri.parse(request.url);
|
|
await widget.onCancel(uri.queryParameters);
|
|
// ignore: use_build_context_synchronously
|
|
Navigator.of(context).pop();
|
|
}
|
|
debugPrint('allowing navigation to ${request.url}');
|
|
return NavigationDecision.navigate;
|
|
},
|
|
onUrlChange: (UrlChange change) {
|
|
debugPrint('url change to ${change.url}');
|
|
},
|
|
),
|
|
)
|
|
..addJavaScriptChannel(
|
|
'Toaster',
|
|
onMessageReceived: (JavaScriptMessage message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(message.message)),
|
|
);
|
|
},
|
|
);
|
|
|
|
// #docregion platform_features
|
|
if (controller.platform is AndroidWebViewController) {
|
|
AndroidWebViewController.enableDebugging(true);
|
|
(controller.platform as AndroidWebViewController)
|
|
.setMediaPlaybackRequiresUserGesture(false);
|
|
}
|
|
// #enddocregion platform_features
|
|
|
|
_controller = controller;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return WillPopScope(
|
|
onWillPop: () async {
|
|
if (pressed < 2) {
|
|
setState(() {
|
|
pressed++;
|
|
});
|
|
final snackBar = SnackBar(
|
|
content: Text(
|
|
'Press back ${3 - pressed} more times to cancel transaction'));
|
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: const Color(0xFF272727),
|
|
leading: GestureDetector(
|
|
child: const Icon(Icons.arrow_back_ios),
|
|
onTap: () => Navigator.pop(context),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withOpacity(0.3),
|
|
borderRadius: BorderRadius.circular(20)),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.lock_outline,
|
|
color: Uri.parse(navUrl).hasScheme
|
|
? Colors.green
|
|
: Colors.blue,
|
|
size: 18,
|
|
),
|
|
const SizedBox(width: 5),
|
|
Expanded(
|
|
child: Text(
|
|
navUrl,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontSize: 14),
|
|
),
|
|
),
|
|
SizedBox(width: pageLoading ? 5 : 0),
|
|
pageLoading
|
|
? const SpinKitFadingCube(
|
|
color: Color(0xFFEB920D),
|
|
size: 10.0,
|
|
)
|
|
: const SizedBox()
|
|
],
|
|
),
|
|
))
|
|
],
|
|
),
|
|
elevation: 0,
|
|
),
|
|
body: SizedBox(
|
|
height: MediaQuery.of(context).size.height,
|
|
width: MediaQuery.of(context).size.width,
|
|
child: loading
|
|
? const Column(
|
|
children: [
|
|
Expanded(
|
|
child: Center(
|
|
child: SpinKitFadingCube(
|
|
color: Color(0xFFEB920D),
|
|
size: 30.0,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: loadingError
|
|
? Column(
|
|
children: [
|
|
Expanded(
|
|
child: Center(
|
|
child: NetworkError(
|
|
loadData: loadPayment,
|
|
message: "Something went wrong,"),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
Expanded(
|
|
child: WebViewWidget(controller: _controller),
|
|
),
|
|
],
|
|
),
|
|
)),
|
|
);
|
|
}
|
|
}
|