Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE-4565: Address the tech debt created by Turbo Promo Code #1370

Merged
merged 12 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 102 additions & 75 deletions lib/turbo/services/payment_service.dart
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reviewer - I just split the method into smaller ones. I've set the nullable parameters to be required so I don't miss passing the argument.

Original file line number Diff line number Diff line change
Expand Up @@ -42,69 +42,18 @@ class PaymentService {
required String currency,
String? promoCode,
}) async {
final acceptedStatusCodes = [200, 202, 204];
late Map<String, dynamic> headers;

if (wallet != null) {
final nonce = const Uuid().v4();
final publicKey = await wallet.getOwner();
final signature = await signNonceAndData(
nonce: nonce,
wallet: wallet,
);

headers = {
'x-nonce': nonce,
'x-signature': signature,
'x-public-key': publicKey,
};
} else {
headers = {};
}

final urlParams = promoCode != null && promoCode.isNotEmpty
? '?promoCode=$promoCode'
: '';

final result = await httpClient
.get(
url: '$turboPaymentUri/v1/price/$currency/$amount$urlParams',
headers: headers)
.onError(
(ArDriveHTTPException error, stackTrace) {
if (error.statusCode == 400) {
logger.d('Invalid promo code: $promoCode');
throw PaymentServiceInvalidPromoCode(promoCode: promoCode);
}
throw PaymentServiceException(
'Turbo price fetch failed with status code ${error.statusCode}',
);
},
final Map<String, dynamic> signatureHeaders =
await _signatureHeadersForGetPriceForFiat(wallet: wallet);
final result = await _requestPriceForFiat(
httpClient,
signatureHeaders: signatureHeaders,
amount: amount,
currency: currency,
turboPaymentUri: turboPaymentUri,
promoCode: promoCode,
);

logger.d('Turbo price fetch status code: ${result.statusCode}');

if (!acceptedStatusCodes.contains(result.statusCode)) {
throw PaymentServiceException(
'Turbo price fetch failed with status code ${result.statusCode}',
);
}

final parsedData = json.decode(result.data);

final winc = BigInt.parse(parsedData['winc']);
final int? actualPaymentAmount = parsedData['actualPaymentAmount'];
final int? quotedPaymentAmount = parsedData['quotedPaymentAmount'];
final adjustments = ((parsedData['adjustments'] ?? const []) as List)
.map((e) => Adjustment.fromJson(e))
.toList();

return PriceForFiat(
winc: winc,
actualPaymentAmount: actualPaymentAmount,
quotedPaymentAmount: quotedPaymentAmount,
adjustments: adjustments,
);
return _parseHttpResponseForPriceForFiat(result);
}

Future<BigInt> getBalance({
Expand Down Expand Up @@ -179,6 +128,93 @@ class PaymentService {
}
}

PriceForFiat _parseHttpResponseForPriceForFiat(
ArDriveHTTPResponse response,
) {
final parsedData = json.decode(response.data);

final winc = BigInt.parse(parsedData['winc']);
final int? actualPaymentAmount = parsedData['actualPaymentAmount'];
final int? quotedPaymentAmount = parsedData['quotedPaymentAmount'];
final adjustments = ((parsedData['adjustments'] ?? const []) as List)
.map((e) => Adjustment.fromJson(e))
.toList();

return PriceForFiat(
winc: winc,
actualPaymentAmount: actualPaymentAmount,
quotedPaymentAmount: quotedPaymentAmount,
adjustments: adjustments,
);
}

Future<ArDriveHTTPResponse> _requestPriceForFiat(
ArDriveHTTP httpClient, {
required Map<String, dynamic> signatureHeaders,
required double amount,
required String currency,
required Uri turboPaymentUri,
required String? promoCode,
}) async {
final acceptedStatusCodes = [200, 202, 204];
final String urlParams = _urlParamsForGetPriceForFiat(promoCode: promoCode);

final result = await httpClient
.get(
url: '$turboPaymentUri/v1/price/$currency/$amount$urlParams',
headers: signatureHeaders,
)
.onError(
(ArDriveHTTPException error, stackTrace) {
if (error.statusCode == 400) {
logger.e('Invalid promo code: $promoCode');
throw PaymentServiceInvalidPromoCode(promoCode: promoCode);
}
throw PaymentServiceException(
'Turbo price fetch failed with exception: $error',
);
},
);

if (!acceptedStatusCodes.contains(result.statusCode)) {
throw PaymentServiceException(
'Turbo price fetch failed with status code ${result.statusCode}',
);
}

return result;
}

Future<Map<String, dynamic>> _signatureHeadersForGetPriceForFiat({
required Wallet? wallet,
}) async {
if (wallet == null) {
return {};
}

final nonce = const Uuid().v4();
final publicKey = await wallet.getOwner();
final signature = await signNonceAndData(
nonce: nonce,
wallet: wallet,
);

return {
'x-nonce': nonce,
'x-signature': signature,
'x-public-key': publicKey,
};
}

String _urlParamsForGetPriceForFiat({
required String? promoCode,
}) {
final urlParams =
promoCode != null && promoCode.isNotEmpty ? '?promoCode=$promoCode' : '';

return urlParams;
}

class DontUsePaymentService implements PaymentService {
@override
late ArDriveHTTP httpClient;
Expand Down Expand Up @@ -230,6 +266,11 @@ class PaymentServiceException implements Exception {
final String message;

PaymentServiceException([this.message = '']);

@override
String toString() {
return 'PaymentServiceException{message: $message}';
}
}

class PaymentServiceInvalidPromoCode implements PaymentServiceException {
Expand All @@ -255,20 +296,6 @@ class PriceForFiat extends Equatable {
return adjustments.first.humanReadableDiscountPercentage;
}

double? get promoDiscountFactor {
thiagocarvalhodev marked this conversation as resolved.
Show resolved Hide resolved
if (adjustments.isEmpty) {
return null;
}

final adjustmentMagnitude = adjustments.first.operatorMagnitude;

// Multiplying by 100 and then dividing by 100 is a workaround for
/// floating point precision issues.
final factor = (100 - (adjustmentMagnitude * 100)) / 100;

return factor;
}

BigInt get winstonCredits => winc;

const PriceForFiat({
Expand Down
4 changes: 2 additions & 2 deletions lib/turbo/topup/blocs/payment_form/payment_form_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
promoCode: promoCode,
);

final isInvalid = promoCode != null &&
final isPromoCodeInvalid = promoCode != null &&
refreshedPriceEstimate.estimate.adjustments.isEmpty;

emit(
Expand All @@ -150,7 +150,7 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
mockExpirationTimeInSeconds: mockExpirationTimeInSeconds,
),
stateAsLoaded.supportedCountries,
isPromoCodeInvalid: isInvalid,
isPromoCodeInvalid: isPromoCodeInvalid,
isFetchingPromoCode: false,
),
);
Expand Down
21 changes: 6 additions & 15 deletions lib/turbo/topup/blocs/payment_form/payment_form_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ abstract class PaymentFormState extends Equatable {
final PriceEstimate priceEstimate;
final int quoteExpirationTimeInSeconds;

bool get hasPromoCodeApplied => priceEstimate.hasPromoCodeApplied;
String? get humanReadableDiscountPercentage =>
priceEstimate.humanReadableDiscountPercentage;
String get paymentAmount => priceEstimate.paymentAmount.toStringAsFixed(2);
BigInt get winstonCredits => priceEstimate.winstonCredits;

@override
List<Object> get props => [
priceEstimate,
Expand Down Expand Up @@ -36,21 +42,6 @@ class PaymentFormLoaded extends PaymentFormState {
final bool isFetchingPromoCode;
final bool errorFetchingPromoCode;

double? get promoDiscountFactor {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reviewer - this code was being unused.

if (priceEstimate.estimate.adjustments.isEmpty) {
return null;
}

final adjustment = priceEstimate.estimate.adjustments.first;
final adjustmentMagnitude = adjustment.operatorMagnitude;

// Multiplying by 100 and then dividing by 100 is a workaround for
/// floating point precision issues.
final factor = (100 - (adjustmentMagnitude * 100)) / 100;

return factor;
}

const PaymentFormLoaded(
super.priceEstimate,
super.quoteExpirationTime,
Expand Down
45 changes: 21 additions & 24 deletions lib/turbo/topup/blocs/topup_estimation_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class TurboTopUpEstimationBloc
promoCode: promoCode,
);
} catch (e, s) {
logger.e('error updating the promo code', e, s);
logger.e('Failed to refresh estimate', e, s);
emit(EstimationLoaded(
balance: stateAsLoaded.balance,
estimatedStorageForBalance:
Expand Down Expand Up @@ -190,31 +190,28 @@ class TurboTopUpEstimationBloc
required String? promoCode,
}) async {
emit(EstimationLoading());
try {
final priceEstimate = turbo.currentPriceEstimate;

final estimatedStorageForBalance =
await turbo.computeStorageEstimateForCredits(
credits: _balance,
outputDataUnit: currentDataUnit,
);
final priceEstimate = turbo.currentPriceEstimate;

emit(
EstimationLoaded(
balance: _balance,
estimatedStorageForBalance:
estimatedStorageForBalance.toStringAsFixed(2),
selectedAmount: priceEstimate.priceInCurrency,
creditsForSelectedAmount: priceEstimate.estimate.winstonCredits,
estimatedStorageForSelectedAmount:
priceEstimate.estimatedStorage.toStringAsFixed(2),
currencyUnit: currentCurrency,
dataUnit: currentDataUnit,
),
);
} catch (e, _) {
rethrow;
}
final estimatedStorageForBalance =
await turbo.computeStorageEstimateForCredits(
credits: _balance,
outputDataUnit: currentDataUnit,
);

emit(
EstimationLoaded(
balance: _balance,
estimatedStorageForBalance:
estimatedStorageForBalance.toStringAsFixed(2),
selectedAmount: priceEstimate.priceInCurrency,
creditsForSelectedAmount: priceEstimate.estimate.winstonCredits,
estimatedStorageForSelectedAmount:
priceEstimate.estimatedStorage.toStringAsFixed(2),
currencyUnit: currentCurrency,
dataUnit: currentDataUnit,
),
);
}

Future<void> _getBalance() async {
Expand Down
11 changes: 10 additions & 1 deletion lib/turbo/topup/models/payment_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,19 @@ class Adjustment extends Equatable {
});

String get humanReadableDiscountPercentage {
final discountPercentage = 100 - (operatorMagnitude * 100);
return discountPercentage.toStringAsFixed(0);
}

double get promoDiscountFactor {
final factor = discountPercentage / 100;
return factor;
}

double get discountPercentage {
final discountPercentage = 100 - (operatorMagnitude * 100);
return discountPercentage;
}

factory Adjustment.fromJson(Map<String, dynamic> json) =>
_$AdjustmentFromJson(json);
Map<String, dynamic> toJson() => _$AdjustmentToJson(this);
Expand Down
8 changes: 8 additions & 0 deletions lib/turbo/topup/models/price_estimate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ class PriceEstimate extends Equatable {
required this.estimatedStorage,
});

bool get hasPromoCodeApplied => estimate.adjustments.isNotEmpty;
String? get humanReadableDiscountPercentage =>
estimate.humanReadableDiscountPercentage;
double get paymentAmount => hasPromoCodeApplied
? estimate.actualPaymentAmount! / 100
: priceInCurrency;
BigInt get winstonCredits => estimate.winstonCredits;

factory PriceEstimate.zero() => PriceEstimate(
estimate: PriceForFiat.zero(),
priceInCurrency: 0,
Expand Down
Loading