Connect customer quotations and payment execution to Sendico (#50)

This commit is contained in:
Stephan D committed 2026-10-16 09:57:28 +02:00
1 parent 5d67c25c8e
commit 7a6510d1a8
45 files changed
+2696 -45

No files matched your search

@@ -0,0 +1,130 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:sxchnge_auth/sxchnge_auth.dart';
import '../mappers/backend.dart';
import '../models/wallet.dart';
import '../transport/backend.dart';
import 'contracts.dart';
import 'http/client.dart';
class BackendWalletService implements WalletService {
BackendWalletService({
required this.baseUri,
required this.accessToken,
http.Client? client,
}) : _client = client ?? createClient() {
if (!baseUri.hasAuthority ||
baseUri.hasQuery ||
baseUri.hasFragment ||
baseUri.userInfo.isNotEmpty ||
(baseUri.scheme != 'https' &&
!(baseUri.scheme == 'http' &&
['localhost', '127.0.0.1'].contains(baseUri.host))))
throw ArgumentError('Valid wallet API URL required');
}
final Uri baseUri;
final String? Function() accessToken;
final http.Client _client;
@override
bool get isSimulated => false;
Future<BackendDTO> send(
String path, {
Map<String, Object?>? body,
bool factor = false,
}) async {
final token = accessToken();
if (token == null) throw const WalletException(WalletError.unavailable);
final uri = baseUri.replace(
path:
'${baseUri.path.replaceFirst(RegExp(r"/$"), "")}/accounts/wallet$path',
);
final request = http.Request(body == null ? 'GET' : 'POST', uri)
..followRedirects = false;
request.headers.addAll({
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
if (kIsWeb) 'X-CSRF-Protection': '1',
});
if (body != null) request.body = jsonEncode(body);
try {
final response = await _client
.send(request)
.then(http.Response.fromStream)
.timeout(const Duration(seconds: 40));
if (token != accessToken())
throw const WalletException(WalletError.unavailable);
final dto = BackendDTO.decode(response.body);
if (response.statusCode < 200 ||
response.statusCode >= 300 ||
!dto.accepted) {
if (factor || dto.error.startsWith('factor'))
throw FactorException(BackendMapper.factorFailure(dto));
throw WalletException(BackendMapper.failure(dto));
}
return dto;
} on FactorException {
rethrow;
} on WalletException {
rethrow;
} catch (_) {
throw const WalletException(WalletError.unavailable);
}
}
Future<T> _mapped<T>(
Future<BackendDTO> dto,
T Function(BackendDTO) map,
) async {
try {
return map(await dto);
} on FactorException {
rethrow;
} on WalletException {
rethrow;
} catch (_) {
throw const WalletException(WalletError.unavailable);
}
}
@override
Future<WalletSnapshot> fetch() => _mapped(send(''), BackendMapper.snapshot);
@override
Future<Quote> quote(TransferRequest request) => _mapped(
send('/quote', body: BackendMapper.request(request)),
BackendMapper.quote,
);
@override
Future<WalletOperation> submit(
String quoteId,
String idempotencyKey, {
String? approval,
}) async {
final value = await _mapped(
send(
'/submit',
body: {'quoteId': quoteId, 'idempotencyKey': idempotencyKey},
),
BackendMapper.operation,
);
if (value == null) throw const WalletException(WalletError.uncertain);
return value;
}
@override
Future<WalletOperation?> findSubmission(String idempotencyKey) => _mapped(
send('/submission', body: {'idempotencyKey': idempotencyKey}),
BackendMapper.operation,
);
@override
Future<AccountProfile> profile() =>
_mapped(send('/profile'), BackendMapper.profile);
@override
Future<AccountProfile> saveLocale(String locale) async =>
throw const WalletException(WalletError.unsupported);
void close() => _client.close();
}