From 3753b16750c9d110cb2fd300e69492915ed4d0e1 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 16 Jun 2022 11:08:39 +0100 Subject: [PATCH] Add ionia merchant sevic --- lib/di.dart | 6 + lib/ionia/ionia_merchant.dart | 170 ++++++++++++++++++++++++++ lib/ionia/ionia_merchant_service.dart | 51 ++++++++ lib/ionia/ionia_token_data.dart | 43 +++++++ lib/ionia/ionia_token_service.dart | 72 +++++++++++ 5 files changed, 342 insertions(+) create mode 100644 lib/ionia/ionia_merchant.dart create mode 100644 lib/ionia/ionia_merchant_service.dart create mode 100644 lib/ionia/ionia_token_data.dart create mode 100644 lib/ionia/ionia_token_service.dart diff --git a/lib/di.dart b/lib/di.dart index 45760f1db..5f4363c33 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -125,6 +125,8 @@ import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart'; +import 'package:cake_wallet/ionia/ionia_merchant_service.dart'; +import 'package:cake_wallet/ionia/ionia_token_service.dart'; final getIt = GetIt.instance; @@ -218,6 +220,10 @@ Future setup( secureStorage: getIt.get(), sharedPreferences: getIt.get())); + getIt.registerFactory(() => IoniaTokenService(getIt.get())); + + getIt.registerFactory(() => IoniaMerchantService(getIt.get(), isDevEnv: true)); + getIt.registerFactoryParam((type, _) => WalletNewVM(getIt.get(), getIt.get(param1: type), _walletInfoSource, diff --git a/lib/ionia/ionia_merchant.dart b/lib/ionia/ionia_merchant.dart new file mode 100644 index 000000000..38758dc0e --- /dev/null +++ b/lib/ionia/ionia_merchant.dart @@ -0,0 +1,170 @@ +import 'package:flutter/foundation.dart'; + +class IoniaMerchant { + IoniaMerchant({ + @required this.id, + @required this.legalName, + @required this.systemName, + @required this.description, + @required this.website, + @required this.termsAndConditions, + @required this.logoUrl, + @required this.cardImageUrl, + @required this.cardholderAgreement, + @required this.purchaseFee, + @required this.revenueShare, + @required this.marketingFee, + @required this.minimumDiscount, + @required this.level1, + @required this.level2, + @required this.level3, + @required this.level4, + @required this.level5, + @required this.level6, + @required this.level7, + @required this.isActive, + @required this.isDeleted, + @required this.isOnline, + @required this.isPhysical, + @required this.isVariablePurchase, + @required this.minimumCardPurchase, + @required this.maximumCardPurchase, + @required this.acceptsTips, + @required this.createdDateFormatted, + @required this.createdBy, + @required this.isRegional, + @required this.modifiedDateFormatted, + @required this.modifiedBy, + @required this.usageInstructions, + @required this.usageInstructionsBak, + @required this.paymentGatewayId, + @required this.giftCardGatewayId, + @required this.isHtmlDescription, + @required this.purchaseInstructions, + @required this.balanceInstructions, + @required this.amountPerCard, + @required this.processingMessage, + @required this.hasBarcode, + @required this.hasInventory, + @required this.isVoidable, + @required this.receiptMessage, + @required this.cssBorderCode, + @required this.paymentInstructions, + @required this.alderSku, + @required this.ngcSku, + @required this.acceptedCurrency, + @required this.deepLink, + @required this.isPayLater + }); + + factory IoniaMerchant.fromJsonMap(Map element) { + return IoniaMerchant( + id: element["Id"] as int, + legalName: element["LegalName"] as String, + systemName: element["SystemName"] as String, + description: element["Description"] as String, + website: element["Website"] as String, + termsAndConditions: element["TermsAndConditions"] as String, + logoUrl: element["LogoUrl"] as String, + cardImageUrl: element["CardImageUrl"] as String, + cardholderAgreement: element["CardholderAgreement"] as String, + purchaseFee: element["PurchaseFee"] as double, + revenueShare: element["RevenueShare"] as double, + marketingFee: element["MarketingFee"] as double, + minimumDiscount: element["MinimumDiscount"] as double, + level1: element["Level1"] as double, + level2: element["Level2"] as double, + level3: element["Level3"] as double, + level4: element["Level4"] as double, + level5: element["Level5"] as double, + level6: element["Level6"] as double, + level7: element["Level7"] as double, + isActive: element["IsActive"] as bool, + isDeleted: element["IsDeleted"] as bool, + isOnline: element["IsOnline"] as bool, + isPhysical: element["IsPhysical"] as bool, + isVariablePurchase: element["IsVariablePurchase"] as bool, + minimumCardPurchase: element["MinimumCardPurchase"] as double, + maximumCardPurchase: element["MaximumCardPurchase"] as double, + acceptsTips: element["AcceptsTips"] as bool, + createdDateFormatted: element["CreatedDate"] as String, + createdBy: element["CreatedBy"] as int, + isRegional: element["IsRegional"] as bool, + modifiedDateFormatted: element["ModifiedDate"] as String, + modifiedBy: element["ModifiedBy"] as int, + usageInstructions: element["UsageInstructions"] as String, + usageInstructionsBak: element["UsageInstructionsBak"] as String, + paymentGatewayId: element["PaymentGatewayId"] as int, + giftCardGatewayId: element["GiftCardGatewayId"] as int , + isHtmlDescription: element["IsHtmlDescription"] as bool, + purchaseInstructions: element["PurchaseInstructions"] as String, + balanceInstructions: element["BalanceInstructions"] as String, + amountPerCard: element["AmountPerCard"] as double, + processingMessage: element["ProcessingMessage"] as String, + hasBarcode: element["HasBarcode"] as bool, + hasInventory: element["HasInventory"] as bool, + isVoidable: element["IsVoidable"] as bool, + receiptMessage: element["ReceiptMessage"] as String, + cssBorderCode: element["CssBorderCode"] as String, + paymentInstructions: element["PaymentInstructions"] as String, + alderSku: element["AlderSku"] as String, + ngcSku: element["NgcSku"] as String, + acceptedCurrency: element["AcceptedCurrency"] as String, + deepLink: element["DeepLink"] as String, + isPayLater: element["IsPayLater"] as bool); + } + + final int id; + final String legalName; + final String systemName; + final String description; + final String website; + final String termsAndConditions; + final String logoUrl; + final String cardImageUrl; + final String cardholderAgreement; + final double purchaseFee; + final double revenueShare; + final double marketingFee; + final double minimumDiscount; + final double level1; + final double level2; + final double level3; + final double level4; + final double level5; + final double level6; + final double level7; + final bool isActive; + final bool isDeleted; + final bool isOnline; + final bool isPhysical; + final bool isVariablePurchase; + final double minimumCardPurchase; + final double maximumCardPurchase; + final bool acceptsTips; + final String createdDateFormatted; + final int createdBy; + final bool isRegional; + final String modifiedDateFormatted; + final int modifiedBy; + final String usageInstructions; + final String usageInstructionsBak; + final int paymentGatewayId; + final int giftCardGatewayId; + final bool isHtmlDescription; + final String purchaseInstructions; + final String balanceInstructions; + final double amountPerCard; + final String processingMessage; + final bool hasBarcode; + final bool hasInventory; + final bool isVoidable; + final String receiptMessage; + final String cssBorderCode; + final String paymentInstructions; + final String alderSku; + final String ngcSku; + final String acceptedCurrency; + final String deepLink; + final bool isPayLater; +} diff --git a/lib/ionia/ionia_merchant_service.dart b/lib/ionia/ionia_merchant_service.dart new file mode 100644 index 000000000..75bb4633d --- /dev/null +++ b/lib/ionia/ionia_merchant_service.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/ionia/ionia_merchant.dart'; +import 'package:cake_wallet/ionia/ionia_token_service.dart'; + +class IoniaMerchantService { + IoniaMerchantService(this._tokenService, {@required this.isDevEnv}); + + static String devApiUrl = "https://apidev.dashdirect.org/partner"; + + final bool isDevEnv; + + final TokenService _tokenService; + + String get apiUrl => isDevEnv ? devApiUrl : ''; + + String get getMerchantsUrl => '$apiUrl/GetMerchants'; + + Future> getMerchants() async { + final token = await _tokenService.getToken(); + // FIX ME: remove hardcoded values + final headers = { + 'Authorization': token.toString(), + 'firstName': 'cake', + 'lastName': 'cake', + 'email': 'cake@test'}; + final response = await post(getMerchantsUrl, headers: headers); + + if (response.statusCode != 200) { + return []; + } + + final decodedBody = json.decode(response.body) as Map; + final isSuccessful = decodedBody['Successful'] as bool ?? false; + + if (!isSuccessful) { + return []; + } + + final data = decodedBody['Data'] as List; + return data.map((dynamic e) { + final element = e as Map; + return IoniaMerchant.fromJsonMap(element); + }).toList(); + } + + Future purchaseGiftCard() async { + + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_token_data.dart b/lib/ionia/ionia_token_data.dart new file mode 100644 index 000000000..1baa4c63d --- /dev/null +++ b/lib/ionia/ionia_token_data.dart @@ -0,0 +1,43 @@ +import 'package:flutter/foundation.dart'; +import 'dart:convert'; + +class IoniaTokenData { + IoniaTokenData({@required this.accessToken, @required this.tokenType, @required this.expiredAt}); + + factory IoniaTokenData.fromJson(String source) { + final decoded = json.decode(source) as Map; + final accessToken = decoded['access_token'] as String; + final expiresIn = decoded['expires_in'] as int; + final tokenType = decoded['token_type'] as String; + final expiredAtInMilliseconds = decoded['expired_at'] as int; + DateTime expiredAt; + + if (expiredAtInMilliseconds != null) { + expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtInMilliseconds); + } else { + expiredAt = DateTime.now().add(Duration(seconds: expiresIn)); + } + + return IoniaTokenData( + accessToken: accessToken, + tokenType: tokenType, + expiredAt: expiredAt); + } + + final String accessToken; + final String tokenType; + final DateTime expiredAt; + + bool get isExpired => DateTime.now().isAfter(expiredAt); + + @override + String toString() => '$tokenType $accessToken'; + + String toJson() { + return json.encode({ + 'access_token': accessToken, + 'token_type': tokenType, + 'expired_at': expiredAt.millisecondsSinceEpoch + }); + } +} \ No newline at end of file diff --git a/lib/ionia/ionia_token_service.dart b/lib/ionia/ionia_token_service.dart new file mode 100644 index 000000000..191595995 --- /dev/null +++ b/lib/ionia/ionia_token_service.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:cake_wallet/ionia/ionia_token_data.dart'; +import 'package:cake_wallet/.secrets.g.dart'; + +String basicAuth(String username, String password) => + 'Basic ' + base64Encode(utf8.encode('$username:$password')); + +abstract class TokenService { + TokenService(this.flutterSecureStorage); + + String get serviceName; + String get oauthUrl; + final FlutterSecureStorage flutterSecureStorage; + + String get _storeKey => '${serviceName}_oauth_token'; + + Future getToken() async { + final storedTokenJson = await flutterSecureStorage.read(key: _storeKey); + IoniaTokenData token; + + if (storedTokenJson != null) { + token = IoniaTokenData.fromJson(storedTokenJson); + } else { + token = await _fetchNewToken(); + await _storeToken(token); + } + + if (token.isExpired) { + token = await _fetchNewToken(); + await _storeToken(token); + } + + return token; + } + + Future _fetchNewToken() async { + final basic = basicAuth(ioniaClientId, ioniaClientSecret); + final body = {'grant_type': 'client_credentials', 'scope': 'cake_dev'}; + final response = await post( + oauthUrl, + headers: { + 'Authorization': basic, + 'Content-Type': 'application/x-www-form-urlencoded'}, + encoding: Encoding.getByName('utf-8'), + body: body); + + if (response.statusCode != 200) { + // throw exception + return null; + } + + return IoniaTokenData.fromJson(response.body); + } + + Future _storeToken(IoniaTokenData token) async { + await flutterSecureStorage.write(key: _storeKey, value: token.toJson()); + } + +} + +class IoniaTokenService extends TokenService { + IoniaTokenService(FlutterSecureStorage flutterSecureStorage) + : super(flutterSecureStorage); + + @override + String get serviceName => 'Ionia'; + + @override + String get oauthUrl => 'https://auth.craypay.com/connect/token'; +} \ No newline at end of file