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/balance_mask/wallets.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/models/auto_refresh_mode.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/payment/auto_refresh.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/payment/quote_helpers.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(); final AutoRefreshScheduler _autoRefresh = AutoRefreshScheduler(); AutoRefreshMode _autoRefreshMode = AutoRefreshMode.on; void update( OrganizationsProvider venue, PaymentAmountProvider payment, WalletsController wallets, PaymentFlowProvider flow, RecipientsProvider recipients, PaymentMethodsProvider _, ) { _organizations = venue; final intent = _intentBuilder.build( payment: payment, wallets: wallets, 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; AutoRefreshMode get autoRefreshMode => _autoRefreshMode; DateTime? get quoteExpiresAt { final expiresAtUnixMs = quotation?.fxQuote?.expiresAtUnixMs; if (expiresAtUnixMs == null) return null; return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true); } Asset? get fee => _assetFromMoney(quoteFeeTotal(quotation)); Asset? get total => _assetFromMoney(quotation?.amounts?.sourceDebitTotal); Asset? get recipientGets => _assetFromMoney(quotation?.amounts?.destinationSettlement); Asset? _assetFromMoney(Money? money) { if (money == null) return null; return createAsset(money.currency, money.amount); } void _setResource(Resource quotation) { _quotation = quotation; _syncAutoRefresh(); notifyListeners(); } void setAutoRefreshMode(AutoRefreshMode mode) { if (_autoRefreshMode == mode) return; _autoRefreshMode = mode; _syncAutoRefresh(); 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'); } void _syncAutoRefresh() { final isEnabled = _autoRefreshMode == AutoRefreshMode.on; _autoRefresh.setEnabled(isEnabled); final canAutoRefresh = isEnabled && canRefresh; _autoRefresh.sync( isLoading: isLoading, canRefresh: canAutoRefresh, scheduledAt: quoteExpiresAt, onRefresh: () async { await refreshQuotation(); }, ); } @override void dispose() { _autoRefresh.dispose(); super.dispose(); } }