Skip to content

Commit

Permalink
Merge pull request #1370 from ardriveapp/PE-4565
Browse files Browse the repository at this point in the history
PE-4565: Address the tech debt created by Turbo Promo Code
  • Loading branch information
matibat authored Oct 10, 2023
2 parents a66f527 + c78f9aa commit 78f59ae
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 131 deletions.
177 changes: 102 additions & 75 deletions lib/turbo/services/payment_service.dart
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 {
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 {
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

0 comments on commit 78f59ae

Please sign in to comment.