import 'package:flutter/foundation.dart'; import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quotes.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/quote/quotes.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/multiple.dart'; import 'package:pshared/utils/exception.dart'; class MultiQuotationProvider extends ChangeNotifier { OrganizationsProvider? _organizations; String? _loadedOrganizationRef; Resource _quotation = Resource(data: null); List? _lastIntents; bool _lastPreviewOnly = false; Map? _lastMetadata; Resource get resource => _quotation; PaymentQuotes? get quotation => _quotation.data; bool get isLoading => _quotation.isLoading; Exception? get error => _quotation.error; bool get canRefresh => _lastIntents != null && _lastIntents!.isNotEmpty; bool get isReady => quotation != null && !_quotation.isLoading && _quotation.error == null; DateTime? get quoteExpiresAt { final quotes = quotation?.quotes; if (quotes == null || quotes.isEmpty) return null; int? minExpiresAt; for (final quote in quotes) { final expiresAtUnixMs = quote.fxQuote?.expiresAtUnixMs; if (expiresAtUnixMs == null) continue; minExpiresAt = minExpiresAt == null ? expiresAtUnixMs : (expiresAtUnixMs < minExpiresAt ? expiresAtUnixMs : minExpiresAt); } if (minExpiresAt == null) return null; return DateTime.fromMillisecondsSinceEpoch(minExpiresAt, isUtc: true); } void update(OrganizationsProvider organizations) { _organizations = organizations; if (!organizations.isOrganizationSet) { reset(); return; } final orgRef = organizations.current.id; if (_loadedOrganizationRef != orgRef) { _loadedOrganizationRef = orgRef; reset(); } } Future quotePayments( List intents, { bool previewOnly = false, String? idempotencyKey, Map? metadata, }) async { final organization = _organizations; if (organization == null || !organization.isOrganizationSet) { throw StateError('Organization is not set'); } if (intents.isEmpty) { throw StateError('At least one payment intent is required'); } _lastIntents = List.from(intents); _lastPreviewOnly = previewOnly; _lastMetadata = metadata == null ? null : Map.from(metadata); _setResource(_quotation.copyWith(isLoading: true, error: null)); try { final response = await MultiplePaymentsService.getQuotation( organization.current.id, QuotePaymentsRequest( idempotencyKey: idempotencyKey ?? const Uuid().v4(), metadata: metadata, intents: intents.map((intent) => intent.toDTO()).toList(), previewOnly: previewOnly, ), ); _setResource( _quotation.copyWith(data: response, isLoading: false, error: null), ); } catch (e) { _setResource( _quotation.copyWith( data: null, isLoading: false, error: toException(e), ), ); } return _quotation.data; } Future refreshQuotation() async { final intents = _lastIntents; if (intents == null || intents.isEmpty) return null; return quotePayments( intents, previewOnly: _lastPreviewOnly, metadata: _lastMetadata, ); } void reset() { _lastIntents = null; _lastPreviewOnly = false; _lastMetadata = null; _quotation = Resource(data: null); notifyListeners(); } void _setResource(Resource quotation) { _quotation = quotation; notifyListeners(); } }