From 5fd971f9c94d964646174362f3d0e015f5590bf0 Mon Sep 17 00:00:00 2001 From: kechankrisna Date: Wed, 24 Nov 2021 00:26:43 +0700 Subject: [PATCH] merge with payway v2 --- .gitignore | 2 +- example/lib/config.dart | 9 +-- example/lib/main.dart | 5 +- example/lib/screens/cart_screen.dart | 25 ++++---- example/pubspec.lock | 7 +++ example/pubspec.yaml | 6 +- lib/model.dart | 1 + lib/model/aba_server_response.dart | 2 +- lib/model/aba_transacition_item.dart | 35 +++++++++++ lib/model/aba_transaction.dart | 92 ++++++++++++++++++---------- lib/service/aba_client_helper.dart | 61 +++++++++++++++--- lib/ui/aba_checkout_container.dart | 5 +- pubspec.yaml | 2 +- test/aba_payment_test.dart | 14 ++++- 14 files changed, 194 insertions(+), 72 deletions(-) create mode 100644 lib/model/aba_transacition_item.dart diff --git a/.gitignore b/.gitignore index e9dc58d..3459b6a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,5 @@ .packages .pub/ - +*.env build/ diff --git a/example/lib/config.dart b/example/lib/config.dart index 404147f..9ebc48b 100644 --- a/example/lib/config.dart +++ b/example/lib/config.dart @@ -1,9 +1,10 @@ import 'package:aba_payment/aba_payment.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; final ABAMerchant merchant = ABAMerchant( - merchantID: "mylekha", - merchantApiName: "pwmylekham", - merchantApiKey: "cea2a8308c634a458b983def9303781f", - baseApiUrl: "https://checkout-uat.payway.com.kh", + merchantID: dotenv.get('ABA_PAYWAY_MERCHANT_ID'), + merchantApiName: dotenv.get('ABA_PAYWAY_MERCHANT_NAME'), + merchantApiKey: dotenv.get('ABA_PAYWAY_API_KEY'), + baseApiUrl: dotenv.get('ABA_PAYWAY_API_URL'), refererDomain: "https://mylekha.app", ); diff --git a/example/lib/main.dart b/example/lib/main.dart index 81c81c6..833e764 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'screens/cart_screen.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: ".env"); runApp(MyApp()); } diff --git a/example/lib/screens/cart_screen.dart b/example/lib/screens/cart_screen.dart index 207d3bc..513bc64 100644 --- a/example/lib/screens/cart_screen.dart +++ b/example/lib/screens/cart_screen.dart @@ -1,6 +1,4 @@ import 'package:aba_payment/aba_payment.dart'; -import 'package:aba_payment/service/aba_client_helper.dart'; -import 'package:aba_payment_example/models/item_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import '../config.dart'; @@ -13,15 +11,15 @@ class CartScreen extends StatefulWidget { class _CartScreenState extends State { bool _isLoading = false; ABAMerchant _merchant = merchant; - double _total = 6.00; double _shipping = 0.0; String _firstname = "Miss"; String _lastname = "My Lekha"; String _phone = "010464144"; String _email = "support@mylekha.app"; - String _checkoutApiUrl = "http://127.0.0.1:8000/api/v1/app/checkout_page"; - List _items = []; + String _checkoutApiUrl = + "http://localhost/api/v1/integrate/payway/checkout_page"; + List _items = []; initialize() { if (mounted) { @@ -32,11 +30,10 @@ class _CartScreenState extends State { _lastname = "My Lekha"; _phone = "010464144"; _email = "support@mylekha.app"; - _checkoutApiUrl = "http://127.0.0.1:8000/api/v1/app/checkout_page"; _items = [ - // ItemModel(name: "item 1", price: 1.00, quantity: 1), - // ItemModel(name: "item 2", price: 2.00, quantity: 2), - // ItemModel(name: "item 3", price: 3.00, quantity: 3), + ABATransactionItem(name: "item 1", price: 1, quantity: 1), + ABATransactionItem(name: "item 2", price: 2, quantity: 1), + ABATransactionItem(name: "item 3", price: 3, quantity: 1), ]; }); } @@ -62,7 +59,7 @@ class _CartScreenState extends State { ..._items.map( (item) => ListTile( title: Text("${item.name}"), - subtitle: Text("price: x${item.price}"), + subtitle: Text("price: x${item.price}\$"), trailing: Text("${item.quantity}"), ), ), @@ -77,7 +74,7 @@ class _CartScreenState extends State { lastname: _lastname, email: _email, phone: _phone, - items: [..._items.map((e) => e.toMap()).toList()], + items: _items, checkoutApiUrl: _checkoutApiUrl, merchant: _merchant, onBeginCheckout: (transaction) { @@ -103,9 +100,9 @@ class _CartScreenState extends State { // onPaymentFail: (transaction) { // print("onPaymentFail ${transaction.toMap()}"); // }, - // onPaymentSuccess: (transaction) { - // print("onPaymentSuccess ${transaction.toMap()}"); - // }, + onPaymentSuccess: (transaction) { + print("onPaymentSuccess ${transaction.toMap()}"); + }, ), ], ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 28c57d9..fc2eab4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -90,6 +90,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" flutter_easyloading: dependency: "direct main" description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9a5e2bb..0e534fe 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + flutter_dotenv: ^5.0.2 dev_dependencies: flutter_test: @@ -41,9 +42,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/lib/model.dart b/lib/model.dart index 6ff90b6..baf59b9 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -1,4 +1,5 @@ export './model/aba_mechant.dart'; export './model/aba_transaction.dart'; +export './model/aba_transacition_item.dart'; export './model/aba_server_response.dart'; export './model/aba_payment.dart'; \ No newline at end of file diff --git a/lib/model/aba_server_response.dart b/lib/model/aba_server_response.dart index ea24b7f..cdbc901 100644 --- a/lib/model/aba_server_response.dart +++ b/lib/model/aba_server_response.dart @@ -21,7 +21,7 @@ class ABAServerResponse { factory ABAServerResponse.fromMap(Map map) { return ABAServerResponse( - status: int.tryParse("${map["status"]}"), + status: map["status"] is int ? map["status"] : -1, description: map["description"], qrString: map["qrString"], qrImage: map["qrImage"], diff --git a/lib/model/aba_transacition_item.dart b/lib/model/aba_transacition_item.dart new file mode 100644 index 0000000..9597a5e --- /dev/null +++ b/lib/model/aba_transacition_item.dart @@ -0,0 +1,35 @@ +class ABATransactionItem { + String name; + double quantity; + double price; + + ABATransactionItem({this.name, this.quantity, this.price}); + + factory ABATransactionItem.fromMap(Map map) { + return ABATransactionItem( + name: map["name"], + quantity: double.tryParse("${map["quantity"]}"), + price: double.tryParse("${map["price"]}"), + ); + } + + copyWith({ + String name, + double quantity, + double price, + }) { + return ABATransactionItem( + name: name ?? this.name, + quantity: quantity ?? this.quantity, + price: price ?? this.price, + ); + } + + Map toMap() { + return { + "name": name, + "quantity": quantity, + "price": price, + }; + } +} diff --git a/lib/model/aba_transaction.dart b/lib/model/aba_transaction.dart index a8be16c..0186b4f 100644 --- a/lib/model/aba_transaction.dart +++ b/lib/model/aba_transaction.dart @@ -4,6 +4,7 @@ import 'package:aba_payment/enumeration.dart'; import 'package:aba_payment/extension.dart'; import 'package:aba_payment/model/aba_mechant.dart'; import 'package:aba_payment/model/aba_payment.dart'; +import 'package:aba_payment/model/aba_transacition_item.dart'; import 'package:aba_payment/service/aba_client_helper.dart'; import 'package:dio/dio.dart'; import 'package:intl/intl.dart'; @@ -12,10 +13,9 @@ import 'aba_server_response.dart'; class ABATransaction { ABAMerchant merchant; - String reqTime; String tranID; double amount; - List> items; + List items; String hash; String firstname; String lastname; @@ -29,10 +29,11 @@ class ABATransaction { AcceptPaymentOption paymentOption; double shipping; + String get reqTime => "${DateFormat("yMddHms").format(DateTime.now())}"; + ABATransaction({ this.merchant, this.tranID, - this.reqTime, this.amount, this.items, this.hash, @@ -53,7 +54,6 @@ class ABATransaction { // var format = DateFormat("yMddHms").format(DateTime.now()); //2021 01 23 234559 OR 2021 11 07 132947 return ABATransaction( merchant: merchant, - reqTime: "${DateFormat("yMddHms").format(DateTime.now())}", tranID: "${(DateTime.now()).microsecondsSinceEpoch}", amount: 0.00, items: [], @@ -70,10 +70,11 @@ class ABATransaction { factory ABATransaction.fromMap(Map map) { return ABATransaction( merchant: ABAMerchant.fromMap(map), - reqTime: map['req_time'], tranID: map["tran_id"], amount: double.tryParse("${map["amount"]}"), - items: List.from(map['items'] ?? []).map((e) => Map.from(e)).toList(), + items: List.from(map['items'] ?? []) + .map((e) => ABATransactionItem.fromMap(e)) + .toList(), hash: map["hash"], firstname: map["firstname"], lastname: map["lastname"], @@ -89,69 +90,84 @@ class ABATransaction { ); } + double get totalPrice { + double result = 0; + this.items.fold(result, (pre, e) => result += e.price * e.quantity); + return result; + } + /// ### [toMap] /// [return] map object Map toMap() { - String _items = - items?.isNotEmpty == true ? base64Encode(utf8.encode("$items")) : ""; - var currency = "USD"; - var _shipping = "0.00"; - var type = "purchase"; + String _encodedItem = ""; + if (this.items?.isNotEmpty == true) { + if (this.amount != this.totalPrice) { + ABAPayment.logger + .error("amount $amount is not equal totalPrice $totalPrice"); + } + var itemText = [...this.items.map((e) => e.toMap()).toList()]; + _encodedItem = base64Encode(json.encode(itemText).runes.toList()); + // _encodedItem = "W3sibmFtZSI6InRlc3QiLCJxdWFudGl0eSI6MSwicHJpY2UiOjZ9XQ=="; + ABAPayment.logger.info("itemText $itemText"); + ABAPayment.logger.info("_encodedItem $_encodedItem"); + } + var _currency = "USD"; + var _type = "purchase"; String _hash = ABAClientHelper(merchant).getHash( reqTime: reqTime, tranID: tranID, amount: "$amount", - items: _items, - shipping: "$_shipping", + items: _encodedItem, + shipping: "$shipping", firstName: firstname, lastName: lastname, email: email, phone: phone, - type: type, + type: _type, paymentOption: paymentOption.toText, - currency: currency, + currency: _currency, ); ABAPayment.logger.info("req_time $reqTime"); ABAPayment.logger.info("tran_id $tranID"); ABAPayment.logger.info("_hash $_hash"); - ABAPayment.logger.info("_items $_items"); ABAPayment.logger.info("amount $amount"); var map = { "req_time": reqTime, "tran_id": tranID, "amount": "$amount", - // "items": "$_items", + "items": "$_encodedItem", "hash": "$_hash", "firstname": "$firstname", "lastname": "$lastname", "phone": "$phone", "email": "$email", - // "return_url": returnUrl, - // "continue_success_url": continueSuccessUrl ?? "", - // "return_params": returnParams ?? "", + "return_url": returnUrl, + "continue_success_url": continueSuccessUrl ?? "", + "return_params": returnParams ?? "", // "return_params": {"tran_id": tranID, "status": 0}, // "phone_country_code": phoneCountryCode ?? "855", // "PreAuth": preAuth, "payment_option": "${paymentOption.toText}", - "shipping": "$_shipping", - "currency": "$currency", + "shipping": "$shipping", + "currency": "$_currency", "merchant_id": "${merchant.merchantID}", - "type": type, + "type": _type, }; return map; } - /// ## `create transaction` - /// + /// ## [create] + /// create a new trasaction Future create() async { var res = ABAServerResponse(status: 11); Map map = this.toMap(); + map["type"] = "purchase"; ABAPayment.logger.debug(map); var formData = FormData.fromMap(map); try { var helper = ABAClientHelper(merchant); var dio = helper.getDio(); - Response response = await dio.post("", data: formData); + Response response = await dio.post("/purchase", data: formData); // ABAPayment.logger.debug(response); try { var map = json.decode(response.data) as Map; @@ -170,21 +186,23 @@ class ABATransaction { return ABAServerResponse(); } - /// ## check transaction - /// + /// ## [check] + /// check the current status of this transaction vai its id Future check() async { var res = ABAServerResponse(status: 11); - FormData form = FormData.fromMap({ - "tran_id": tranID, - "hash": - ABAClientHelper(merchant).getHash(reqTime: reqTime, tranID: tranID), - }); + var hash = + ABAClientHelper(merchant).getHash(reqTime: reqTime, tranID: tranID); + FormData form = FormData.fromMap({"tran_id": tranID, "hash": hash}); var helper = ABAClientHelper(merchant); try { Response response = await helper.getDio().post("/check-transaction", data: form); var map = json.decode(response.data) as Map; + ABAPayment.logger.error("checkMap $map"); res = ABAServerResponse.fromMap(map); + if (res.status != 0) { + res.description = ABAClientHelper.handleTransactionResponse(res.status); + } return res; } catch (error, stacktrace) { print("Exception occured: $error stackTrace: $stacktrace"); @@ -192,4 +210,12 @@ class ABATransaction { } return ABAServerResponse(); } + + /// ## [validate] + /// will return true if transaction completed + /// otherwise false + Future validate() async { + var result = await this.check(); + return (result?.status == 0); + } } diff --git a/lib/service/aba_client_helper.dart b/lib/service/aba_client_helper.dart index fad6d1b..1434848 100644 --- a/lib/service/aba_client_helper.dart +++ b/lib/service/aba_client_helper.dart @@ -19,8 +19,7 @@ class ABAClientHelper { /// ``` Dio getDio() { Dio dio = Dio(); - dio.options.baseUrl = - "${merchant.baseApiUrl}/api/payment-gateway/v1/payments/purchase"; + dio.options.baseUrl = merchant.baseApiUrl; dio.options.connectTimeout = 60 * 1000; //60 seconds dio.options.receiveTimeout = 60 * 1000; //60 seconds @@ -114,22 +113,64 @@ class ABAClientHelper { /// [handleTransactionResponse] /// /// `This will be describe response from each transaction based on status code` - String handleTransactionResponse(int status) { + static String handleTransactionResponse(int status) { switch (status) { - case 0: - return "success"; - break; case 1: - return "invalid hash"; + return "Invalid Hash, Hash generated is incorrect and not following the guideline to generate the Hash."; break; case 2: - return "invalid hash"; + return "Invalid Transaction ID, unsupported characters included in Transaction ID"; break; case 3: - return "invalid amount"; + return "Invalid Amount format need not include decimal point for KHR transaction. example for USD 100.00 for KHR 100"; break; case 4: - return "duplicate tran_id"; + return "Duplicate Transaction ID, the transaction ID already exists in PayWay, generate new transaction."; + break; + case 5: + return "Invalid Continue Success URL, (Main domain must be registered in PayWay backend to use success URL)"; + break; + case 6: + return "Invalid Domain Name (Request originated from non-whitelisted domain need to register domain in PayWay backend)"; + break; + case 7: + return "Invalid Return Param (String must be lesser than 500 chars)"; + break; + case 9: + return "Invalid Limit Amount (The amount must be smaller than value that allowed in PayWay backend)"; + break; + case 10: + return "Invalid Shipping Amount"; + break; + case 11: + return "PayWay Server Side Error"; + break; + case 12: + return "Invalid Currency Type (Merchant is allowed only one currency - USD or KHR)"; + break; + case 13: + return "Invalid Item, value for items parameters not following the guideline to generate the base 64 encoded array of item list."; + break; + case 15: + return "Invalid Channel Values for parameter topup_channel"; + break; + case 16: + return "Invalid First Name - unsupported special characters included in value"; + break; + case 17: + return "Invalid Last Name"; + break; + case 18: + return "Invalid Phone Number"; + break; + case 19: + return "Invalid Email Address"; + break; + case 20: + return "Required purchase details when checkout"; + break; + case 21: + return "Expired production key"; break; default: return "other - server-side error"; diff --git a/lib/ui/aba_checkout_container.dart b/lib/ui/aba_checkout_container.dart index ead3d96..a45c014 100644 --- a/lib/ui/aba_checkout_container.dart +++ b/lib/ui/aba_checkout_container.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:aba_payment/enumeration.dart'; import 'package:aba_payment/model.dart'; +import 'package:aba_payment/model/aba_transacition_item.dart'; import 'package:aba_payment/service/strings.dart'; import 'package:aba_payment/ui/aba_checkout_success.dart'; import 'package:aba_payment/ui/aba_checkout_webview.dart'; @@ -42,7 +43,7 @@ class ABACheckoutContainer extends StatefulWidget { final String phone; /// `items` respresent list of items(json format) that user selected - final List> items; + final List items; /// `checkoutApiUrl` respresent checkout api link from the server side final String checkoutApiUrl; @@ -253,7 +254,7 @@ class _ABACheckoutContainerState extends State } else { uri = Uri.http(parsed.authority, parsed.path, map); } - ABAPayment.logger.error(uri); + ABAPayment.logger.info(uri); widget.onFinishCheckout?.call(_transaction); await Navigator.push( context, diff --git a/pubspec.yaml b/pubspec.yaml index 9a782e2..5d78baa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: url_launcher: ^6.0.4 # launch any scheme easy_logger: ^0.0.2 # bueaty logger fluttertoast: ^8.0.7 # easy to handle toast bar - + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/aba_payment_test.dart b/test/aba_payment_test.dart index 644019f..ef8908a 100644 --- a/test/aba_payment_test.dart +++ b/test/aba_payment_test.dart @@ -1,4 +1,5 @@ import 'package:flutter/services.dart'; +import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -6,7 +7,16 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); - tearDown(() { - channel.setMockMethodCallHandler(null); + test("encoded item", () { + List> items = [ + {"name": "test", "quantity": 1, "price": 6} + ]; + + var j = json.encode(items); + String actual = base64Encode(j.runes.toList()); + var matcher = "W3sibmFtZSI6InRlc3QiLCJxdWFudGl0eSI6MSwicHJpY2UiOjZ9XQ"; + print(actual); + // expect(base64Decode(actual), base64Decode(matcher)); + expect(1, 1); }); }