Files
enaklo-flutter/lib/infrastructure/wallet/wallet_response.dart
T
efrilmandClaude Opus 5.5 f5e3075205 EnakPoint & EnakCoin wallet, PIN, outlet, order, community, reward
- Wallet EnakPoint & EnakCoin: beranda, riwayat, saldo kedaluwarsa, kode
  bayar, tukar EnakCoin, transfer
- PIN (buat, ganti, lupa), outlet, riwayat order, game spin
- Halaman community, webview, coming soon, dan ikon menu beranda baru
- Redesign Reward Saya: kartu tiket, filter chip, detail kode + QR
- Hapus customer point loader, menu, dan checkout lama
- Base URL ke enaklo-pos-api.altru.id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:43:22 +07:00

94 lines
3.1 KiB
Dart

import 'package:dio/dio.dart';
import '../../domain/wallet/wallet.dart';
/// Hitung mundur bawaan bila server menjawab `429` tanpa header `Retry-After`.
const int kDefaultOtpRetrySeconds = 60;
/// Membaca envelope `{ success, data, errors }`. Mengembalikan `null` bila
/// sukses, atau [WalletFailure] yang sesuai bila gagal.
WalletFailure? parseWalletFailure(Response response) {
if (response.statusCode == 401) return const WalletFailure.unauthorized();
final body = response.data;
if (body is! Map) return _failureFromStatus(response);
if (body['success'] == true) return null;
// Error PIN: `data` berisi `code` dan field tambahan.
final data = body['data'];
if (data is Map) {
final pinFailure = _pinFailure(data['code']?.toString(), data);
if (pinFailure != null) return pinFailure;
}
final errors = body['errors'];
final first = errors is List && errors.isNotEmpty && errors.first is Map
? errors.first as Map
: null;
final code = first?['code']?.toString();
switch (code) {
case '303':
case '310':
return const WalletFailure.invalidRequest();
case '304':
return WalletFailure.rejected(first?['cause']?.toString() ?? '');
case '404':
return const WalletFailure.notFound();
case '429':
return WalletFailure.tooManyRequests(_retryAfterSeconds(response));
case '900':
return const WalletFailure.internalError();
}
return _pinFailure(code, const {}) ?? _failureFromStatus(response);
}
/// Isi `data` dari envelope sukses. Beberapa endpoint (mis. `/wallet`)
/// membungkus lagi payload-nya dalam `{ status, message, data }`, jadi lapisan
/// itu ikut dikupas.
Map<String, dynamic> walletDataOf(Response response) {
final data = response.data['data'] as Map;
final isInnerEnvelope =
data.containsKey('status') &&
data.containsKey('message') &&
data['data'] is Map;
return Map<String, dynamic>.from(isInnerEnvelope ? data['data'] : data);
}
WalletFailure? _pinFailure(String? code, Map data) {
switch (code) {
case 'PIN_NOT_SET':
return const WalletFailure.pinNotSet();
case 'PIN_INVALID':
return WalletFailure.pinInvalid(_toInt(data['remaining_attempts']));
case 'PIN_LOCKED':
return WalletFailure.pinLocked(_toDateTime(data['locked_until']));
case 'TRANSFER_BLOCKED':
return WalletFailure.transferBlocked(
_toDateTime(data['transfer_blocked_until']),
);
}
return null;
}
WalletFailure _failureFromStatus(Response response) {
final status = response.statusCode ?? 0;
if (status == 404) return const WalletFailure.notFound();
if (status == 429) {
return WalletFailure.tooManyRequests(_retryAfterSeconds(response));
}
if (status >= 500) return const WalletFailure.internalError();
return const WalletFailure.unexpectedError();
}
int _retryAfterSeconds(Response response) =>
int.tryParse(response.headers.value('retry-after') ?? '') ??
kDefaultOtpRetrySeconds;
int? _toInt(Object? value) =>
value is int ? value : int.tryParse(value?.toString() ?? '');
DateTime? _toDateTime(Object? value) =>
value == null ? null : DateTime.tryParse(value.toString());