import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/asset.dart'; import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/models/money.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/provider/payment/quotation/intent_builder.dart'; import 'package:pshared/service/payment/quotation.dart'; import 'package:pshared/utils/exception.dart'; class QuotationProvider extends ChangeNotifier { static final _logger = Logger('provider.payment.quotation'); Resource _quotation = Resource( data: null, isLoading: false, error: null, ); late OrganizationsProvider _organizations; bool _isLoaded = false; PaymentIntent? _lastIntent; final QuotationIntentBuilder _intentBuilder = QuotationIntentBuilder(); void update( OrganizationsProvider venue, PaymentAmountProvider payment, PaymentSourceController sources, PaymentFlowProvider flow, RecipientsProvider recipients, PaymentMethodsProvider _, ) { _organizations = venue; final intent = _intentBuilder.build( payment: payment, sources: sources, flow: flow, recipients: recipients, ); if (intent == null) return; final intentKey = _buildIntentKey(intent); final lastIntent = _lastIntent; if (lastIntent != null && intentKey == _buildIntentKey(lastIntent)) return; getQuotation(intent); } PaymentQuote? get quotation => _quotation.data; bool get isLoading => _quotation.isLoading; Exception? get error => _quotation.error; bool get canRefresh => _lastIntent != null; bool get isReady => _isLoaded && !_quotation.isLoading && _quotation.error == null; DateTime? get quoteExpiresAt { final expiresAtUnixMs = quotation?.fxQuote?.expiresAtUnixMs; if (expiresAtUnixMs == null) return null; return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true); } Asset? get fee => _assetFromMoney(quotation?.expectedFeeTotal); Asset? get total => _assetFromMoney(quotation?.debitAmount); Asset? get recipientGets => _assetFromMoney(quotation?.expectedSettlementAmount); Asset? _assetFromMoney(Money? money) { if (money == null) return null; return createAsset(money.currency, money.amount); } void _setResource(Resource quotation) { _quotation = quotation; notifyListeners(); } Future refreshQuotation() async { final intent = _lastIntent; if (intent == null) return null; return getQuotation(intent); } Future getQuotation(PaymentIntent intent) async { if (!_organizations.isOrganizationSet) { throw StateError('Organization is not set'); } _lastIntent = intent; try { _setResource(_quotation.copyWith(isLoading: true, error: null)); final response = await QuotationService.getQuotation( _organizations.current.id, QuotePaymentRequest( idempotencyKey: Uuid().v4(), intent: intent.toDTO(), ), ); _isLoaded = true; _setResource( _quotation.copyWith(data: response, isLoading: false, error: null), ); } catch (e, st) { _logger.warning('Failed to get quotation', e, st); _setResource( _quotation.copyWith( data: null, error: toException(e), isLoading: false, ), ); } return _quotation.data; } void reset() { _isLoaded = false; _lastIntent = null; _setResource(Resource(data: null, isLoading: false, error: null)); } String _buildIntentKey(PaymentIntent intent) { final payload = jsonEncode(intent.toDTO().toJson()); return Uuid().v5(Namespace.url.value, 'quote:$payload'); } }