Flutter 中的 Paypal 支付网关的集成
问题:
在撰写本文时,Paypal 还没有为 Flutter 中的支付网关集成提供 SDK 包。 此外,整个互联网上貌似都没有解决此问题的方法。
在对该主题进行了大量研究之后,我觉得我找到了解决此问题的简单方法。
解决方案:
我已经使用 WebView 成功实现了 Paypal 支付网关。 我使用了一些 Paypal 的可用 API 来进行支付,这些 API 可以正式集成到网站中。
所以让我们从编码部分开始。
首先,我们需要创建一个名为 PaypalServices.dart 的 dart 文件,其中包含使用 Paypal 执行交易所需的所有 API。
下面给出了代码。 您可以复制这些代码来创建 dart 文件。
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert' as convert;
import 'package:http_auth/http_auth.dart';
class PaypalServices {
String domain = "https://api.sandbox.paypal.com"; // for sandbox mode
// String domain = "https://api.paypal.com"; // for production mode
// change clientId and secret with your own, provided by paypal
String clientId = 'YOUR CLIENT ID';
String secret = 'YOUR SECRET';
// for getting the access token from Paypal
Future<String> getAccessToken() async {
try {
var client = BasicAuthClient(clientId, secret);
var response = await client.post('$domain/v1/oauth2/token?grant_type=client_credentials');
if (response.statusCode == 200) {
final body = convert.jsonDecode(response.body);
return body["access_token"];
}
return null;
} catch (e) {
rethrow;
}
}
// for creating the payment request with Paypal
Future<Map<String, String>> createPaypalPayment(
transactions, accessToken) async {
try {
var response = await http.post("$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 null;
} else {
throw Exception(body["message"]);
}
} catch (e) {
rethrow;
}
}
// for executing the payment transaction
Future<String> executePayment(url, payerId, accessToken) async {
try {
var response = await http.post(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 body["id"];
}
return null;
} catch (e) {
rethrow;
}
}
}
现在,我们需要创建一个名为 PaypalPayment.dart 的 dart 文件,其中包含带有 Paypal URL 的 WebView 小部件,必须在其中执行交易。
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'PaypalServices.dart';
class PaypalPayment extends StatefulWidget {
final Function onFinish;
PaypalPayment({this.onFinish});
State<StatefulWidget> createState() {
return PaypalPaymentState();
}
}
class PaypalPaymentState extends State<PaypalPayment> {
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String checkoutUrl;
String executeUrl;
String accessToken;
PaypalServices services = PaypalServices();
// you can change default currency according to your need
Map<dynamic,dynamic> defaultCurrency = {"symbol": "USD ", "decimalDigits": 2, "symbolBeforeTheNumber": true, "currency": "USD"};
bool isEnableShipping = false;
bool isEnableAddress = false;
String returnURL = 'return.example.com';
String cancelURL= 'cancel.example.com';
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
try {
accessToken = await services.getAccessToken();
final transactions = getOrderParams();
final res =
await services.createPaypalPayment(transactions, accessToken);
if (res != null) {
setState(() {
checkoutUrl = res["approvalUrl"];
executeUrl = res["executeUrl"];
});
}
} catch (e) {
print('exception: '+e.toString());
final snackBar = SnackBar(
content: Text(e.toString()),
duration: Duration(seconds: 10),
action: SnackBarAction(
label: 'Close',
onPressed: () {
// Some code to undo the change.
},
),
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
});
}
// item name, price and quantity
String itemName = 'iPhone X';
String itemPrice = '1.99';
int quantity = 1;
Map<String, dynamic> getOrderParams() {
List items = [
{
"name": itemName,
"quantity": quantity,
"price": itemPrice,
"currency": defaultCurrency["currency"]
}
];
// checkout invoice details
String totalAmount = '1.99';
String subTotalAmount = '1.99';
String shippingCost = '0';
int shippingDiscountCost = 0;
String userFirstName = 'Gulshan';
String userLastName = 'Yadav';
String addressCity = 'Delhi';
String addressStreet = 'Mathura Road';
String addressZipCode = '110014';
String addressCountry = 'India';
String addressState = 'Delhi';
String addressPhoneNumber = '+919990119091';
Map<String, dynamic> temp = {
"intent": "sale",
"payer": {"payment_method": "paypal"},
"transactions": [
{
"amount": {
"total": totalAmount,
"currency": defaultCurrency["currency"],
"details": {
"subtotal": subTotalAmount,
"shipping": shippingCost,
"shipping_discount":
((-1.0) * shippingDiscountCost).toString()
}
},
"description": "The payment transaction description.",
"payment_options": {
"allowed_payment_method": "INSTANT_FUNDING_SOURCE"
},
"item_list": {
"items": items,
if (isEnableShipping &&
isEnableAddress)
"shipping_address": {
"recipient_name": userFirstName +
" " +
userLastName,
"line1": addressStreet,
"line2": "",
"city": addressCity,
"country_code": addressCountry,
"postal_code": addressZipCode,
"phone": addressPhoneNumber,
"state": addressState
},
}
}
],
"note_to_payer": "Contact us for any questions on your order.",
"redirect_urls": {
"return_url": returnURL,
"cancel_url": cancelURL
}
};
return temp;
}
Widget build(BuildContext context) {
print(checkoutUrl);
if (checkoutUrl != null) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).backgroundColor,
leading: GestureDetector(
child: Icon(Icons.arrow_back_ios),
onTap: () => Navigator.pop(context),
),
),
body: WebView(
initialUrl: checkoutUrl,
javascriptMode: JavascriptMode.unrestricted,
navigationDelegate: (NavigationRequest request) {
if (request.url.contains(returnURL)) {
final uri = Uri.parse(request.url);
final payerID = uri.queryParameters['PayerID'];
if (payerID != null) {
services
.executePayment(executeUrl, payerID, accessToken)
.then((id) {
widget.onFinish(id);
Navigator.of(context).pop();
});
} else {
Navigator.of(context).pop();
}
Navigator.of(context).pop();
}
if (request.url.contains(cancelURL)) {
Navigator.of(context).pop();
}
return NavigationDecision.navigate;
},
),
);
} else {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
}),
backgroundColor: Colors.black12,
elevation: 0.0,
),
body: Center(child: Container(child: CircularProgressIndicator())),
);
}
}
}
在创建和配置这两个给定的上述文件之后。 我们需要从我们的主文件调用 PaypalPayment 小部件。
在这里,我创建了一个简单易懂的 makePayment.dart 文件,其中包含一个按钮,通过单击您将被重定向到 PaypalPayment 小部件。
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'PaypalPayment.dart';
class makePayment extends StatefulWidget {
_makePaymentState createState() => _makePaymentState();
}
class _makePaymentState extends State<makePayment> {
TextStyle style = TextStyle(fontFamily: 'Open Sans', fontSize: 15.0);
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: new Scaffold(
backgroundColor: Colors.transparent,
key: _scaffoldKey,
appBar: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: new AppBar(
backgroundColor: Colors.white,
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Paypal Payment Example',
style: TextStyle(
fontSize: 16.0,
color: Colors.red[900],
fontWeight: FontWeight.bold,
fontFamily: 'Open Sans'),
),
],
),
),
),
body:Container(
width: MediaQuery.of(context).size.width,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: (){
// make PayPal payment
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => PaypalPayment(
onFinish: (number) async {
// payment done
print('order id: '+number);
},
),
),
);
},
child: Text('Pay with Paypal', textAlign: TextAlign.center,),
),
],
),
)
),
)
);
}
}
就这么简单,要不要赶紧去试试?