Skip to content

Commit 3f62d39

Browse files
authored
Merge pull request SatoshiPortal#2272 from SatoshiPortal/funding-fixes-6110
hotfix: Funding fixes 6.11.0
2 parents 4e286cd + 62ac199 commit 3f62d39

38 files changed

Lines changed: 788 additions & 52 deletions

lib/core/widgets/cards/backup_card.dart

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class BackupCard extends StatelessWidget {
2323
borderRadius: BorderRadius.circular(2),
2424
),
2525
child: Row(
26+
crossAxisAlignment: CrossAxisAlignment.start,
2627
children: [
2728
Image.asset(
2829
Assets.misc.passwordbook.path,
@@ -31,22 +32,24 @@ class BackupCard extends StatelessWidget {
3132
color: context.appColors.onSecondary,
3233
),
3334
const Gap(16),
34-
Column(
35-
crossAxisAlignment: .start,
36-
children: [
37-
BBText(
38-
context.loc.backupCardTitle,
39-
style: context.font.bodyMedium,
40-
color: context.appColors.onSecondary,
41-
),
42-
BBText(
43-
context.loc.backupCardSubtitle,
44-
style: context.font.bodyMedium,
45-
color: context.appColors.onSecondary,
46-
),
47-
],
35+
Expanded(
36+
child: Column(
37+
crossAxisAlignment: .start,
38+
children: [
39+
BBText(
40+
context.loc.backupCardTitle,
41+
style: context.font.bodyMedium,
42+
color: context.appColors.onSecondary,
43+
),
44+
BBText(
45+
context.loc.backupCardSubtitle,
46+
style: context.font.bodyMedium,
47+
color: context.appColors.onSecondary,
48+
),
49+
],
50+
),
4851
),
49-
const Spacer(),
52+
const Gap(8),
5053
Icon(Icons.arrow_forward, color: context.appColors.onSecondary),
5154
],
5255
),

lib/features/fund_exchange/adapters/funding_gateway/bullbitcoin_api_funding_gateway.dart

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,21 @@ class BullBitcoinApiFundingGateway implements FundingGatewayPort {
6464
apiError?['en']?.toString() ??
6565
error['message']?.toString() ??
6666
'Unknown API error';
67-
log.severe(
68-
message: '$method API error [$errorCode]: $errorMessage',
69-
error: FetchFundingDetailsFailed(message: errorMessage),
67+
final messageData = _parseMessageData(apiError?['messageData']);
68+
log.warning(
69+
'$method API error [$errorCode]: $errorMessage',
70+
error: FetchFundingDetailsFailed(
71+
code: errorCode,
72+
message: errorMessage,
73+
messageData: messageData,
74+
),
7075
trace: StackTrace.current,
7176
);
72-
throw FetchFundingDetailsFailed(message: errorMessage);
77+
throw FetchFundingDetailsFailed(
78+
code: errorCode,
79+
message: errorMessage,
80+
messageData: messageData,
81+
);
7382
}
7483

7584
try {
@@ -88,19 +97,30 @@ class BullBitcoinApiFundingGateway implements FundingGatewayPort {
8897
message: 'Missing funding details in response',
8998
);
9099
}
91-
final element = result['element'] as Map<String, dynamic>;
100+
final element = (result['element'] as Map<String, dynamic>?) ?? {};
92101
final ppExtraData =
93102
(result['ppExtraData'] as Map<String, dynamic>?) ?? {};
103+
final merged = {...element, ...ppExtraData}.map(
104+
(key, value) =>
105+
MapEntry(key, value is num ? value.toString() : value),
106+
);
107+
merged['numTelefono'] ??=
108+
merged['NUM TELEFONO'] ??
109+
merged['NUM_TELEFONO'] ??
110+
merged['PHONE NUMBER'] ??
111+
merged['phoneNumber'];
94112
return GetFundingDetailsResponseModel.fromJson(
95-
{...element, ...ppExtraData},
113+
merged,
96114
).toDomain(method: fundingMethod);
115+
} on FetchFundingDetailsFailed {
116+
rethrow;
97117
} catch (e, stackTrace) {
98-
log.severe(
99-
message: 'Error parsing funding details response',
118+
log.warning(
119+
'Error parsing funding details response',
100120
error: e,
101121
trace: stackTrace,
102122
);
103-
rethrow;
123+
throw const FetchFundingDetailsFailed(message: 'Could not parse details');
104124
}
105125
}
106126

@@ -124,15 +144,27 @@ class BullBitcoinApiFundingGateway implements FundingGatewayPort {
124144

125145
final error = resp.data['error'];
126146
if (error != null) {
147+
final apiError = error['data']?['apiError'];
148+
final errorCode = apiError?['code']?.toString() ?? '';
149+
final errorMessage =
150+
apiError?['en']?.toString() ??
151+
error['message']?.toString() ??
152+
'Unknown API error';
127153
throw FetchInstitutionsFailed(
128-
message: error['message']?.toString() ?? 'Unknown API error',
154+
message: errorMessage,
155+
code: errorCode.isEmpty ? null : errorCode,
156+
messageData: _parseMessageData(apiError?['messageData']),
129157
);
130158
}
131159

132160
final result = resp.data['result'];
133-
if (result is! Map<String, dynamic>) return [];
161+
if (result is! Map<String, dynamic>) {
162+
throw const FetchInstitutionsFailed.emptyList();
163+
}
134164
final elements = result['elements'] as List<dynamic>?;
135-
if (elements == null) return [];
165+
if (elements == null || elements.isEmpty) {
166+
throw const FetchInstitutionsFailed.emptyList();
167+
}
136168

137169
return elements
138170
.map((e) {
@@ -153,6 +185,13 @@ class BullBitcoinApiFundingGateway implements FundingGatewayPort {
153185
.toList();
154186
}
155187

188+
Map<String, String>? _parseMessageData(dynamic data) {
189+
if (data is! Map) return null;
190+
return data.map(
191+
(key, value) => MapEntry(key.toString(), value?.toString() ?? ''),
192+
);
193+
}
194+
156195
@override
157196
Future<void> registerResponsibilityConsent() async {
158197
final resp = await _authenticatedApiClient.post(

lib/features/fund_exchange/application/fund_exchange_application_error.dart

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,34 @@ class UnsupportedJurisdiction extends FundExchangeApplicationError {
3030

3131
class FetchFundingDetailsFailed extends FundExchangeApplicationError {
3232
final String message;
33+
final String? code;
34+
final Map<String, String>? messageData;
3335

34-
const FetchFundingDetailsFailed({required this.message});
36+
const FetchFundingDetailsFailed({
37+
required this.message,
38+
this.code,
39+
this.messageData,
40+
});
3541
}
3642

3743
class FetchInstitutionsFailed extends FundExchangeApplicationError {
3844
final String message;
45+
final String? code;
46+
final Map<String, String>? messageData;
47+
final bool emptyList;
3948

40-
const FetchInstitutionsFailed({required this.message});
49+
const FetchInstitutionsFailed({
50+
required this.message,
51+
this.code,
52+
this.messageData,
53+
this.emptyList = false,
54+
});
55+
56+
const FetchInstitutionsFailed.emptyList()
57+
: message = '',
58+
code = null,
59+
messageData = null,
60+
emptyList = true;
4161
}
4262

4363
class FundExchangeUnknownError extends FundExchangeApplicationError {

lib/features/fund_exchange/presentation/bloc/fund_exchange_bloc.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class FundExchangeBloc extends Bloc<FundExchangeEvent, FundExchangeState> {
8383
ListFundingInstitutionsQuery(jurisdictionCode: event.jurisdiction.code),
8484
);
8585

86+
if (result.institutions.isEmpty) {
87+
throw const FetchInstitutionsFailed.emptyList();
88+
}
89+
8690
emit(state.copyWith(fundingInstitutions: result.institutions));
8791
} on FundExchangeApplicationError catch (e) {
8892
emit(
@@ -245,6 +249,12 @@ class FundExchangeBloc extends Bloc<FundExchangeEvent, FundExchangeState> {
245249
FundExchangeFundingDetailsErrorCleared event,
246250
Emitter<FundExchangeState> emit,
247251
) async {
248-
emit(state.copyWith(getExchangeFundingDetailsException: null));
252+
emit(
253+
state.copyWith(
254+
getExchangeFundingDetailsException: null,
255+
listFundingInstitutionsException: null,
256+
fundingInstitutions: event.resetInstitutions ? null : state.fundingInstitutions,
257+
),
258+
);
249259
}
250260
}

lib/features/fund_exchange/presentation/bloc/fund_exchange_event.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ sealed class FundExchangeEvent with _$FundExchangeEvent {
1313
FundExchangeScamWarningConsentSubmitted;
1414
const factory FundExchangeEvent.scamWarningDismissed() =
1515
FundExchangeScamWarningDismissed;
16-
const factory FundExchangeEvent.fundingDetailsErrorCleared() =
17-
FundExchangeFundingDetailsErrorCleared;
16+
const factory FundExchangeEvent.fundingDetailsErrorCleared({
17+
@Default(false) bool resetInstitutions,
18+
}) = FundExchangeFundingDetailsErrorCleared;
1819
const FundExchangeEvent._();
1920
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'package:bb_mobile/generated/l10n/localization.dart';
2+
3+
class FundExchangeApiErrorCopy {
4+
FundExchangeApiErrorCopy._();
5+
6+
static String? title(String? code, AppLocalizations loc) => switch (code) {
7+
'ERR_ORD_PO404' => loc.fundExchangeErrorTitleOrdPo404,
8+
'ERR_RCP_PO404' => loc.fundExchangeErrorTitleRcpPo404,
9+
'ERR_RCP_POSINPE404' => loc.fundExchangeErrorTitleRcpPosinpe404,
10+
'ERR_ORD_KYC400' => loc.fundExchangeErrorTitleOrdKyc400,
11+
'ERR_ORD_COP400' => loc.fundExchangeErrorTitleOrdCop400,
12+
'ERR_ORD_CSRCP400' => loc.fundExchangeErrorTitleOrdCsrcp400,
13+
'ERR_RCP_400' => loc.fundExchangeErrorTitleRcp400,
14+
_ => null,
15+
};
16+
17+
static String message({
18+
required String? code,
19+
required String backendMessage,
20+
required Map<String, String>? messageData,
21+
required AppLocalizations loc,
22+
}) {
23+
if (code == 'ERR_RCP_400') {
24+
return backendMessage.isNotEmpty
25+
? backendMessage
26+
: loc.fundExchangeErrorRcp400;
27+
}
28+
29+
return switch (code) {
30+
'ERR_ORD_PO404' => loc.fundExchangeErrorOrdPo404,
31+
'ERR_RCP_PO404' => loc.fundExchangeErrorRcpPo404,
32+
'ERR_RCP_POSINPE404' => loc.fundExchangeErrorRcpPosinpe404,
33+
'ERR_ORD_KYC400' => loc.fundExchangeErrorOrdKyc400(
34+
messageData?['missingFields'] ?? '',
35+
),
36+
'ERR_ORD_COP400' => loc.fundExchangeErrorOrdCop400(
37+
messageData?['error'] ?? backendMessage,
38+
),
39+
'ERR_ORD_CSRCP400' => loc.fundExchangeErrorOrdCsrcp400(
40+
messageData?['iban'] ?? '',
41+
),
42+
_ => loc.fundExchangeErrorLoadingDetails,
43+
};
44+
}
45+
}

0 commit comments

Comments
 (0)