- 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>
94 lines
3.1 KiB
Dart
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());
|