new change to use intaleq_map sdk 04-16-4
This commit is contained in:
331
packages/flutter_paypal/lib/flutter_paypal.dart
Normal file
331
packages/flutter_paypal/lib/flutter_paypal.dart
Normal file
@@ -0,0 +1,331 @@
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
109
packages/flutter_paypal/lib/src/PaypalServices.dart
Normal file
109
packages/flutter_paypal/lib/src/PaypalServices.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
// ignore_for_file: file_names
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:async';
|
||||
import 'dart:convert' as convert;
|
||||
import 'package:http_auth/http_auth.dart';
|
||||
|
||||
class PaypalServices {
|
||||
final String clientId, secretKey;
|
||||
final bool sandboxMode;
|
||||
PaypalServices({
|
||||
required this.clientId,
|
||||
required this.secretKey,
|
||||
required this.sandboxMode,
|
||||
});
|
||||
|
||||
getAccessToken() async {
|
||||
String domain = sandboxMode
|
||||
? "https://api.sandbox.paypal.com"
|
||||
: "https://api.paypal.com";
|
||||
try {
|
||||
var client = BasicAuthClient(clientId, secretKey);
|
||||
var response = await client.post(
|
||||
Uri.parse("$domain/v1/oauth2/token?grant_type=client_credentials"));
|
||||
if (response.statusCode == 200) {
|
||||
final body = convert.jsonDecode(response.body);
|
||||
return {
|
||||
'error': false,
|
||||
'message': "Success",
|
||||
'token': body["access_token"]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'error': true,
|
||||
'message': "Your PayPal credentials seems incorrect"
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
'error': true,
|
||||
'message': "Unable to proceed, check your internet connection."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map> createPaypalPayment(transactions, accessToken) async {
|
||||
String domain = sandboxMode
|
||||
? "https://api.sandbox.paypal.com"
|
||||
: "https://api.paypal.com";
|
||||
try {
|
||||
var response = await http.post(Uri.parse("$domain/v1/payments/payment"),
|
||||
body: convert.jsonEncode(transactions),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
|
||||
final body = convert.jsonDecode(response.body);
|
||||
if (response.statusCode == 201) {
|
||||
if (body["links"] != null && body["links"].length > 0) {
|
||||
List links = body["links"];
|
||||
|
||||
String executeUrl = "";
|
||||
String approvalUrl = "";
|
||||
final item = links.firstWhere((o) => o["rel"] == "approval_url",
|
||||
orElse: () => null);
|
||||
if (item != null) {
|
||||
approvalUrl = item["href"];
|
||||
}
|
||||
final item1 = links.firstWhere((o) => o["rel"] == "execute",
|
||||
orElse: () => null);
|
||||
if (item1 != null) {
|
||||
executeUrl = item1["href"];
|
||||
}
|
||||
return {"executeUrl": executeUrl, "approvalUrl": approvalUrl};
|
||||
}
|
||||
return {};
|
||||
} else {
|
||||
return body;
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map> executePayment(url, payerId, accessToken) async {
|
||||
try {
|
||||
var response = await http.post(Uri.parse(url),
|
||||
body: convert.jsonEncode({"payer_id": payerId}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
'Authorization': 'Bearer $accessToken'
|
||||
});
|
||||
|
||||
final body = convert.jsonDecode(response.body);
|
||||
if (response.statusCode == 200) {
|
||||
return {'error': false, 'message': "Success", 'data': body};
|
||||
} else {
|
||||
return {
|
||||
'error': true,
|
||||
'message': "Payment inconclusive.",
|
||||
'data': body
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return {'error': true, 'message': e, 'exception': true, 'data': null};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.6 KiB |
18
packages/flutter_paypal/lib/src/assets/icons/lock.svg
Normal file
18
packages/flutter_paypal/lib/src/assets/icons/lock.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="24" height="28" viewBox="0 0 24 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M5.75 11.75C5.75 11.1977 6.19772 10.75 6.75 10.75H17.25C17.8023 10.75 18.25 11.1977 18.25 11.75V17.25C18.25 18.3546 17.3546 19.25 16.25 19.25H7.75C6.64543 19.25 5.75 18.3546 5.75 17.25V11.75Z" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.75001 10.5V10.3427C7.75001 8.78147 7.65609 7.04125 8.74648 5.9239C9.3683 5.2867 10.3745 4.75 12 4.75C13.6255 4.75 14.6317 5.2867 15.2536 5.9239C16.3439 7.04125 16.25 8.78147 16.25 10.3427V10.5" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="-4" y="0" width="32" height="32" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/flutter_paypal/lib/src/assets/img/cloud_state.png
Normal file
BIN
packages/flutter_paypal/lib/src/assets/img/cloud_state.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
50
packages/flutter_paypal/lib/src/errors/network_error.dart
Normal file
50
packages/flutter_paypal/lib/src/errors/network_error.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NetworkError extends StatelessWidget {
|
||||
final Function loadData;
|
||||
final String message;
|
||||
final bool isSmall;
|
||||
|
||||
const NetworkError(
|
||||
{super.key, required this.loadData, required this.message, this.isSmall = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
"lib/src/assets/img/cloud_state.png",
|
||||
package: "flutter_paypal",
|
||||
height: 120,
|
||||
),
|
||||
SizedBox(
|
||||
height: isSmall ? 20 : 40,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(message,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF272727),
|
||||
fontWeight: FontWeight.w400)),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => loadData(),
|
||||
child: const Text("Tap to retry",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w800)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
117
packages/flutter_paypal/lib/src/screens/complete_payment.dart
Normal file
117
packages/flutter_paypal/lib/src/screens/complete_payment.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_paypal/src/errors/network_error.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
|
||||
import '../PaypalServices.dart';
|
||||
|
||||
class CompletePayment extends StatefulWidget {
|
||||
final Function onSuccess, onCancel, onError;
|
||||
final PaypalServices services;
|
||||
final String url, executeUrl, accessToken;
|
||||
const CompletePayment({
|
||||
Key? key,
|
||||
required this.onSuccess,
|
||||
required this.onError,
|
||||
required this.onCancel,
|
||||
required this.services,
|
||||
required this.url,
|
||||
required this.executeUrl,
|
||||
required this.accessToken,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_CompletePaymentState createState() => _CompletePaymentState();
|
||||
}
|
||||
|
||||
class _CompletePaymentState extends State<CompletePayment> {
|
||||
bool loading = true;
|
||||
bool loadingError = false;
|
||||
|
||||
complete() async {
|
||||
final uri = Uri.parse(widget.url);
|
||||
final payerID = uri.queryParameters['PayerID'];
|
||||
if (payerID != null) {
|
||||
Map params = {
|
||||
"payerID": payerID,
|
||||
"paymentId": uri.queryParameters['paymentId'],
|
||||
"token": uri.queryParameters['token'],
|
||||
};
|
||||
setState(() {
|
||||
loading = true;
|
||||
loadingError = false;
|
||||
});
|
||||
|
||||
Map resp = await widget.services
|
||||
.executePayment(widget.executeUrl, payerID, widget.accessToken);
|
||||
if (resp['error'] == false) {
|
||||
params['status'] = 'success';
|
||||
params['data'] = resp['data'];
|
||||
await widget.onSuccess(params);
|
||||
setState(() {
|
||||
loading = false;
|
||||
loadingError = false;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
if (resp['exception'] != null && resp['exception'] == true) {
|
||||
widget.onError({"message": resp['message']});
|
||||
setState(() {
|
||||
loading = false;
|
||||
loadingError = true;
|
||||
});
|
||||
} else {
|
||||
await widget.onError(resp['data']);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
//return NavigationDecision.prevent;
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
complete();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
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: complete,
|
||||
message: "Something went wrong,"),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const Center(
|
||||
child: Text("Payment Completed"),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user