feat: integrate enakgame and voucher
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s

This commit is contained in:
efrilm
2026-10-10 02:33:16 +07:00
parent e5515d5871
commit 38850b281f
104 changed files with 7122 additions and 8063 deletions
@@ -1,81 +0,0 @@
import 'dart:developer';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/function/app_function.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/game/game.dart';
import '../game_dtos.dart';
@injectable
class GameRemoteDataProvider {
final ApiClient _apiClient;
final String _logName = "GameRemoteDataProvider";
GameRemoteDataProvider(this._apiClient);
Future<DC<GameFailure, GameDto>> ferrisWheel() async {
try {
final response = await _apiClient.get(
ApiPath.ferrisWheel,
headers: getAuthorizationHeader(),
);
if (response.data['code'] == 401) {
return DC.error(
GameFailure.serverError(ApiFailure.unauthorized('Session Expired')),
);
}
if (response.data['status'] == false) {
return DC.error(
GameFailure.dynamicErrorMessage('Terjadi kesalahan coba lagi nanti'),
);
}
final dto = GameDto.fromJson(response.data['data']['data']['game']);
return DC.data(dto);
} on ApiFailure catch (e, s) {
log('ferrisWheel', name: _logName, error: e, stackTrace: s);
return DC.error(GameFailure.serverError(e));
}
}
Future<DC<GameFailure, SpinResultDto>> spin({required String gameId}) async {
try {
final response = await _apiClient.post(
ApiPath.spin,
headers: getAuthorizationHeader(),
data: {'spin_id': gameId},
);
if (response.data is! Map || response.data['success'] != true) {
final errors = response.data is Map ? response.data['errors'] : null;
final code = errors is List && errors.isNotEmpty && errors.first is Map
? errors.first['code']?.toString()
: null;
return DC.error(
GameFailure.dynamicErrorMessage(
code == '304'
? 'EnakCoin kurang, game sedang tidak aktif, atau hadiah baru '
'saja habis. EnakCoin kamu tidak terpotong, silakan coba lagi.'
: 'Terjadi kesalahan, coba lagi',
),
);
}
final dto = SpinResultDto.fromJson(
Map<String, dynamic>.from(response.data['data'] as Map),
);
return DC.data(dto);
} on ApiFailure catch (e, s) {
log('spin', name: _logName, error: e, stackTrace: s);
return DC.error(GameFailure.serverError(e));
}
}
}
-31
View File
@@ -1,31 +0,0 @@
part of '../game_dtos.dart';
@freezed
abstract class GameDto with _$GameDto {
const factory GameDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'name') String? name,
@JsonKey(name: 'type') String? type,
@JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'prizes') List<GamePrizeDto>? prizes,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
}) = _GameDto;
factory GameDto.fromJson(Map<String, dynamic> json) =>
_$GameDtoFromJson(json);
const GameDto._();
Game toDomain() => Game(
id: id ?? '',
name: name ?? '',
type: type ?? '',
isActive: isActive ?? false,
metadata: metadata ?? {},
prizes: prizes?.map((e) => e.toDomain()).toList() ?? [],
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
);
}
@@ -1,27 +0,0 @@
part of '../game_dtos.dart';
@freezed
abstract class GamePrizeDto with _$GamePrizeDto {
const factory GamePrizeDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'game_id') String? gameId,
@JsonKey(name: 'name') String? name,
@JsonKey(name: 'metadata') Map<String, dynamic>? metadata,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
}) = _GamePrizeDto;
factory GamePrizeDto.fromJson(Map<String, dynamic> json) =>
_$GamePrizeDtoFromJson(json);
const GamePrizeDto._();
GamePrize toDomain() => GamePrize(
id: id ?? '',
gameId: gameId ?? '',
name: name ?? '',
metadata: metadata ?? {},
createdAt: createdAt ?? '',
updatedAt: updatedAt ?? '',
);
}
@@ -1,47 +0,0 @@
part of '../game_dtos.dart';
@freezed
abstract class SpinResultDto with _$SpinResultDto {
const factory SpinResultDto({
@JsonKey(name: 'game_play') GamePlayDto? gamePlay,
@JsonKey(name: 'prize_won') PrizeWonDto? prizeWon,
@JsonKey(name: 'coins_remaining') int? coinsRemaining,
}) = _SpinResultDto;
factory SpinResultDto.fromJson(Map<String, dynamic> json) =>
_$SpinResultDtoFromJson(json);
const SpinResultDto._();
SpinResult toDomain() => SpinResult(
gamePlayId: gamePlay?.id ?? '',
coinsUsed: gamePlay?.coinsUsed ?? 0,
prizeId: prizeWon?.id ?? '',
prizeName: prizeWon?.name ?? '',
coinsRemaining: coinsRemaining ?? 0,
);
}
@freezed
abstract class GamePlayDto with _$GamePlayDto {
const factory GamePlayDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'game_id') String? gameId,
@JsonKey(name: 'coins_used') int? coinsUsed,
@JsonKey(name: 'created_at') String? createdAt,
}) = _GamePlayDto;
factory GamePlayDto.fromJson(Map<String, dynamic> json) =>
_$GamePlayDtoFromJson(json);
}
@freezed
abstract class PrizeWonDto with _$PrizeWonDto {
const factory PrizeWonDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'name') String? name,
}) = _PrizeWonDto;
factory PrizeWonDto.fromJson(Map<String, dynamic> json) =>
_$PrizeWonDtoFromJson(json);
}
-10
View File
@@ -1,10 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/game/game.dart';
part 'game_dtos.freezed.dart';
part 'game_dtos.g.dart';
part 'dto/game_dto.dart';
part 'dto/game_prize_dto.dart';
part 'dto/spin_result_dto.dart';
File diff suppressed because it is too large Load Diff
-90
View File
@@ -1,90 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_dtos.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_GameDto _$GameDtoFromJson(Map<String, dynamic> json) => _GameDto(
id: json['id'] as String?,
name: json['name'] as String?,
type: json['type'] as String?,
isActive: json['is_active'] as bool?,
metadata: json['metadata'] as Map<String, dynamic>?,
prizes: (json['prizes'] as List<dynamic>?)
?.map((e) => GamePrizeDto.fromJson(e as Map<String, dynamic>))
.toList(),
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> _$GameDtoToJson(_GameDto instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'type': instance.type,
'is_active': instance.isActive,
'metadata': instance.metadata,
'prizes': instance.prizes,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
};
_GamePrizeDto _$GamePrizeDtoFromJson(Map<String, dynamic> json) =>
_GamePrizeDto(
id: json['id'] as String?,
gameId: json['game_id'] as String?,
name: json['name'] as String?,
metadata: json['metadata'] as Map<String, dynamic>?,
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> _$GamePrizeDtoToJson(_GamePrizeDto instance) =>
<String, dynamic>{
'id': instance.id,
'game_id': instance.gameId,
'name': instance.name,
'metadata': instance.metadata,
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
};
_SpinResultDto _$SpinResultDtoFromJson(Map<String, dynamic> json) =>
_SpinResultDto(
gamePlay: json['game_play'] == null
? null
: GamePlayDto.fromJson(json['game_play'] as Map<String, dynamic>),
prizeWon: json['prize_won'] == null
? null
: PrizeWonDto.fromJson(json['prize_won'] as Map<String, dynamic>),
coinsRemaining: (json['coins_remaining'] as num?)?.toInt(),
);
Map<String, dynamic> _$SpinResultDtoToJson(_SpinResultDto instance) =>
<String, dynamic>{
'game_play': instance.gamePlay,
'prize_won': instance.prizeWon,
'coins_remaining': instance.coinsRemaining,
};
_GamePlayDto _$GamePlayDtoFromJson(Map<String, dynamic> json) => _GamePlayDto(
id: json['id'] as String?,
gameId: json['game_id'] as String?,
coinsUsed: (json['coins_used'] as num?)?.toInt(),
createdAt: json['created_at'] as String?,
);
Map<String, dynamic> _$GamePlayDtoToJson(_GamePlayDto instance) =>
<String, dynamic>{
'id': instance.id,
'game_id': instance.gameId,
'coins_used': instance.coinsUsed,
'created_at': instance.createdAt,
};
_PrizeWonDto _$PrizeWonDtoFromJson(Map<String, dynamic> json) =>
_PrizeWonDto(id: json['id'] as String?, name: json['name'] as String?);
Map<String, dynamic> _$PrizeWonDtoToJson(_PrizeWonDto instance) =>
<String, dynamic>{'id': instance.id, 'name': instance.name};
@@ -1,50 +0,0 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/game/game.dart';
import '../datasources/remote_data_provider.dart';
@Injectable(as: IGameRepository)
class GameRepository implements IGameRepository {
final GameRemoteDataProvider _remoteDataProvider;
final String _logName = 'GameRepository';
GameRepository(this._remoteDataProvider);
@override
Future<Either<GameFailure, Game>> ferrisWheel() async {
try {
final result = await _remoteDataProvider.ferrisWheel();
if (result.hasError) {
return left(result.error!);
}
final data = result.data!.toDomain();
return right(data);
} catch (e, s) {
log('ferrisWheel', name: _logName, error: e, stackTrace: s);
return left(const GameFailure.unexpectedError());
}
}
@override
Future<Either<GameFailure, SpinResult>> spin({required String gameId}) async {
try {
final result = await _remoteDataProvider.spin(gameId: gameId);
if (result.hasError) {
return left(result.error!);
}
return right(result.data!.toDomain());
} catch (e, s) {
log('spin', name: _logName, error: e, stackTrace: s);
return left(const GameFailure.unexpectedError());
}
}
}
@@ -0,0 +1,101 @@
import 'dart:developer';
import 'package:data_channel/data_channel.dart';
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/api/interceptors/sensitive_aware_log_interceptor.dart';
import '../../../common/function/app_function.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/wallet/wallet.dart';
import '../wallet_dtos.dart';
import '../wallet_response.dart';
/// Katalog, tukar, dan riwayat voucher (§9).
@injectable
class VoucherRemoteDataProvider {
final ApiClient _apiClient;
final String _logName = 'VoucherRemoteDataProvider';
VoucherRemoteDataProvider(this._apiClient);
Future<DC<WalletFailure, List<VoucherDto>>> fetchVouchers() => _request(
'fetchVouchers',
() => _apiClient.get(ApiPath.vouchers, headers: getAuthorizationHeader()),
(response) => ((response.data as Map)['data'] as List? ?? [])
.map((e) => VoucherDto.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
);
Future<DC<WalletFailure, VoucherRedemptionDto>> redeem({
required String voucherId,
required String pin,
required String idempotencyKey,
}) => _request(
'redeem',
() => _apiClient.post(
ApiPath.voucherRedeem(voucherId),
headers: {...getAuthorizationHeader(), 'Idempotency-Key': idempotencyKey},
data: {'pin': pin},
extra: {kSensitiveRequest: true},
),
(response) => VoucherRedemptionDto.fromJson(walletDataOf(response)),
);
Future<DC<WalletFailure, VoucherRedemptionPageDto>> fetchRedemptions({
required int page,
required int limit,
}) => _request(
'fetchRedemptions',
() => _apiClient.get(
ApiPath.voucherRedemptions,
headers: getAuthorizationHeader(),
params: {'page': page, 'limit': limit},
),
_parseRedemptionPage,
);
/// `data` bisa berupa list langsung dengan `pagination` di envelope, atau
/// objek `{ data, pagination }`.
VoucherRedemptionPageDto _parseRedemptionPage(Response response) {
final body = response.data as Map;
final payload = body['data'];
final rawItems = payload is Map ? payload['data'] : payload;
final rawPagination = payload is Map
? payload['pagination']
: body['pagination'];
return VoucherRedemptionPageDto(
items: (rawItems as List? ?? [])
.map(
(e) => VoucherRedemptionDto.fromJson(
Map<String, dynamic>.from(e as Map),
),
)
.toList(),
pagination: rawPagination is Map
? WalletPaginationDto.fromJson(
Map<String, dynamic>.from(rawPagination),
)
: const WalletPaginationDto(),
);
}
Future<DC<WalletFailure, T>> _request<T>(
String name,
Future<Response> Function() call,
T Function(Response response) parse,
) async {
try {
final response = await call();
final failure = parseWalletFailure(response);
if (failure != null) return DC.error(failure);
return DC.data(parse(response));
} on ApiFailure catch (e, s) {
log(name, name: _logName, error: e, stackTrace: s);
return DC.error(WalletFailure.serverError(e));
}
}
}
@@ -60,19 +60,6 @@ class WalletRemoteDataProvider {
(response) => WalletExpiringDto.fromJson(walletDataOf(response)),
);
Future<DC<WalletFailure, PaymentCodeDto>> createPaymentCode({
required String pin,
}) => _request(
'createPaymentCode',
() => _apiClient.post(
ApiPath.walletPaymentCode,
headers: getAuthorizationHeader(),
data: {'pin': pin},
extra: {kSensitiveRequest: true},
),
(response) => PaymentCodeDto.fromJson(walletDataOf(response)),
);
Future<DC<WalletFailure, ExchangePreviewDto>> previewExchange({
required int coins,
}) => _request(
@@ -100,7 +100,6 @@ abstract class CustomerOrderPaymentDto with _$CustomerOrderPaymentDto {
@JsonKey(name: 'method_type') String? methodType,
@JsonKey(name: 'amount') int? amount,
@JsonKey(name: 'refund_amount') int? refundAmount,
@JsonKey(name: 'points_used') int? pointsUsed,
}) = _CustomerOrderPaymentDto;
factory CustomerOrderPaymentDto.fromJson(Map<String, dynamic> json) =>
@@ -114,7 +113,6 @@ abstract class CustomerOrderPaymentDto with _$CustomerOrderPaymentDto {
methodType: methodType ?? '',
amount: amount ?? 0,
refundAmount: refundAmount ?? 0,
pointsUsed: pointsUsed,
);
}
@@ -6,7 +6,6 @@ abstract class OutletDto with _$OutletDto {
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'name') String? name,
@JsonKey(name: 'address') String? address,
@JsonKey(name: 'accepts_point_payment') bool? acceptsPointPayment,
@JsonKey(name: 'earns_points') bool? earnsPoints,
@JsonKey(name: 'earns_coins') bool? earnsCoins,
}) = _OutletDto;
@@ -20,7 +19,6 @@ abstract class OutletDto with _$OutletDto {
id: id ?? '',
name: name ?? '',
address: (address?.trim().isEmpty ?? true) ? null : address!.trim(),
acceptsPointPayment: acceptsPointPayment ?? false,
earnsPoints: earnsPoints ?? false,
earnsCoins: earnsCoins ?? false,
);
@@ -1,24 +0,0 @@
part of '../wallet_dtos.dart';
@freezed
abstract class PaymentCodeDto with _$PaymentCodeDto {
const factory PaymentCodeDto({
@JsonKey(name: 'code') String? code,
@JsonKey(name: 'qr_payload') String? qrPayload,
@JsonKey(name: 'expires_at') String? expiresAt,
}) = _PaymentCodeDto;
factory PaymentCodeDto.fromJson(Map<String, dynamic> json) =>
_$PaymentCodeDtoFromJson(json);
const PaymentCodeDto._();
PaymentCode toDomain() => PaymentCode(
code: code ?? '',
qrPayload: qrPayload ?? '',
// Kode berlaku 2 menit; dipakai hanya bila server tidak mengirim expires_at.
expiresAt:
_parseDateTime(expiresAt) ??
DateTime.now().add(const Duration(minutes: 2)),
);
}
@@ -0,0 +1,103 @@
part of '../wallet_dtos.dart';
/// `terms` belum dibaca: bentuknya masih disepakati dengan tim backoffice,
/// sementara itu yang ditampilkan `description` saja (§9.1).
@freezed
abstract class VoucherDto with _$VoucherDto {
const factory VoucherDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'name') String? name,
@JsonKey(name: 'description') String? description,
@JsonKey(name: 'image_url') String? imageUrl,
@JsonKey(name: 'voucher_type') String? voucherType,
@JsonKey(name: 'face_value') int? faceValue,
@JsonKey(name: 'point_cost') int? pointCost,
@JsonKey(name: 'max_per_customer') int? maxPerCustomer,
@JsonKey(name: 'valid_until') String? validUntil,
@JsonKey(name: 'available') int? available,
}) = _VoucherDto;
factory VoucherDto.fromJson(Map<String, dynamic> json) =>
_$VoucherDtoFromJson(json);
const VoucherDto._();
Voucher toDomain() => Voucher(
id: id ?? '',
name: name ?? '',
description: (description?.trim().isEmpty ?? true) ? null : description,
imageUrl: (imageUrl?.isEmpty ?? true) ? null : imageUrl,
type: VoucherType.fromApi(voucherType),
faceValue: faceValue ?? 0,
pointCost: pointCost ?? 0,
maxPerCustomer: maxPerCustomer,
validUntil: _parseDateTime(validUntil),
available: available,
);
}
/// Item "Voucher saya" dan response tukar voucher; [pointBalance] dan
/// [replayed] hanya ada di response tukar.
@freezed
abstract class VoucherRedemptionDto with _$VoucherRedemptionDto {
const factory VoucherRedemptionDto({
@JsonKey(name: 'id') String? id,
@JsonKey(name: 'voucher_id') String? voucherId,
@JsonKey(name: 'voucher_name') String? voucherName,
@JsonKey(name: 'voucher_image_url') String? voucherImageUrl,
@JsonKey(name: 'voucher_type') String? voucherType,
@JsonKey(name: 'status') String? status,
@JsonKey(name: 'face_value') int? faceValue,
@JsonKey(name: 'point_cost') int? pointCost,
@JsonKey(name: 'code') String? code,
@JsonKey(name: 'code_expires_at') String? codeExpiresAt,
@JsonKey(name: 'completed_at') String? completedAt,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'point_balance') int? pointBalance,
@JsonKey(name: 'replayed') bool? replayed,
}) = _VoucherRedemptionDto;
factory VoucherRedemptionDto.fromJson(Map<String, dynamic> json) =>
_$VoucherRedemptionDtoFromJson(json);
const VoucherRedemptionDto._();
VoucherRedemption toDomain() => VoucherRedemption(
id: id ?? '',
voucherId: voucherId ?? '',
voucherName: voucherName ?? '',
voucherImageUrl: (voucherImageUrl?.isEmpty ?? true)
? null
: voucherImageUrl,
voucherType: VoucherType.fromApi(voucherType),
status: VoucherRedemptionStatus.fromApi(status),
faceValue: faceValue ?? 0,
pointCost: pointCost ?? 0,
code: (code?.isEmpty ?? true) ? null : code,
codeExpiresAt: _parseDateTime(codeExpiresAt),
completedAt: _parseDateTime(completedAt),
createdAt: _parseDateTime(createdAt),
);
VoucherRedeemResult toRedeemResult() => VoucherRedeemResult(
redemption: toDomain(),
pointBalance: pointBalance ?? 0,
replayed: replayed ?? false,
);
}
class VoucherRedemptionPageDto {
final List<VoucherRedemptionDto> items;
final WalletPaginationDto pagination;
const VoucherRedemptionPageDto({
required this.items,
required this.pagination,
});
VoucherRedemptionPage toDomain() => VoucherRedemptionPage(
items: items.map((e) => e.toDomain()).toList(),
page: pagination.page ?? 1,
totalPages: pagination.totalPages ?? 1,
);
}
@@ -0,0 +1,64 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/wallet/wallet.dart';
import '../datasources/voucher_remote_data_provider.dart';
@Injectable(as: IVoucherRepository)
class VoucherRepository implements IVoucherRepository {
final VoucherRemoteDataProvider _remoteDataProvider;
final String _logName = 'VoucherRepository';
VoucherRepository(this._remoteDataProvider);
@override
Future<Either<WalletFailure, List<Voucher>>> getVouchers() => _run(
'getVouchers',
_remoteDataProvider.fetchVouchers,
(dtos) => dtos.map((e) => e.toDomain()).toList(),
);
@override
Future<Either<WalletFailure, VoucherRedeemResult>> redeem({
required String voucherId,
required String pin,
required String idempotencyKey,
}) => _run(
'redeem',
() => _remoteDataProvider.redeem(
voucherId: voucherId,
pin: pin,
idempotencyKey: idempotencyKey,
),
(dto) => dto.toRedeemResult(),
);
@override
Future<Either<WalletFailure, VoucherRedemptionPage>> getRedemptions({
required int page,
int limit = 20,
}) => _run(
'getRedemptions',
() => _remoteDataProvider.fetchRedemptions(page: page, limit: limit),
(dto) => dto.toDomain(),
);
Future<Either<WalletFailure, R>> _run<D, R>(
String name,
Future<DC<WalletFailure, D>> Function() call,
R Function(D dto) toDomain,
) async {
try {
final result = await call();
if (result.hasError) return left(result.error!);
return right(toDomain(result.data as D));
} catch (e, s) {
log(name, name: _logName, error: e, stackTrace: s);
return left(const WalletFailure.unexpectedError());
}
}
}
@@ -50,15 +50,6 @@ class WalletRepository implements IWalletRepository {
(dto) => dto.toDomain(),
);
@override
Future<Either<WalletFailure, PaymentCode>> createPaymentCode({
required String pin,
}) => _run(
'createPaymentCode',
() => _remoteDataProvider.createPaymentCode(pin: pin),
(dto) => dto.toDomain(),
);
@override
Future<Either<WalletFailure, ExchangePreview>> previewExchange({
required int coins,
+1 -1
View File
@@ -8,12 +8,12 @@ part 'wallet_dtos.g.dart';
part 'dto/wallet_summary_dto.dart';
part 'dto/wallet_transaction_dto.dart';
part 'dto/wallet_expiring_dto.dart';
part 'dto/payment_code_dto.dart';
part 'dto/exchange_dto.dart';
part 'dto/transfer_dto.dart';
part 'dto/pin_dto.dart';
part 'dto/outlet_dto.dart';
part 'dto/customer_order_dto.dart';
part 'dto/voucher_dto.dart';
DateTime? _parseDateTime(String? value) =>
value == null ? null : DateTime.tryParse(value);
File diff suppressed because it is too large Load Diff
+65 -18
View File
@@ -112,20 +112,6 @@ _WalletExpiringDto _$WalletExpiringDtoFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$WalletExpiringDtoToJson(_WalletExpiringDto instance) =>
<String, dynamic>{'point': instance.point, 'coin': instance.coin};
_PaymentCodeDto _$PaymentCodeDtoFromJson(Map<String, dynamic> json) =>
_PaymentCodeDto(
code: json['code'] as String?,
qrPayload: json['qr_payload'] as String?,
expiresAt: json['expires_at'] as String?,
);
Map<String, dynamic> _$PaymentCodeDtoToJson(_PaymentCodeDto instance) =>
<String, dynamic>{
'code': instance.code,
'qr_payload': instance.qrPayload,
'expires_at': instance.expiresAt,
};
_ExchangePreviewDto _$ExchangePreviewDtoFromJson(Map<String, dynamic> json) =>
_ExchangePreviewDto(
coinAmount: (json['coin_amount'] as num?)?.toInt(),
@@ -253,7 +239,6 @@ _OutletDto _$OutletDtoFromJson(Map<String, dynamic> json) => _OutletDto(
id: json['id'] as String?,
name: json['name'] as String?,
address: json['address'] as String?,
acceptsPointPayment: json['accepts_point_payment'] as bool?,
earnsPoints: json['earns_points'] as bool?,
earnsCoins: json['earns_coins'] as bool?,
);
@@ -263,7 +248,6 @@ Map<String, dynamic> _$OutletDtoToJson(_OutletDto instance) =>
'id': instance.id,
'name': instance.name,
'address': instance.address,
'accepts_point_payment': instance.acceptsPointPayment,
'earns_points': instance.earnsPoints,
'earns_coins': instance.earnsCoins,
};
@@ -358,7 +342,6 @@ _CustomerOrderPaymentDto _$CustomerOrderPaymentDtoFromJson(
methodType: json['method_type'] as String?,
amount: (json['amount'] as num?)?.toInt(),
refundAmount: (json['refund_amount'] as num?)?.toInt(),
pointsUsed: (json['points_used'] as num?)?.toInt(),
);
Map<String, dynamic> _$CustomerOrderPaymentDtoToJson(
@@ -369,5 +352,69 @@ Map<String, dynamic> _$CustomerOrderPaymentDtoToJson(
'method_type': instance.methodType,
'amount': instance.amount,
'refund_amount': instance.refundAmount,
'points_used': instance.pointsUsed,
};
_VoucherDto _$VoucherDtoFromJson(Map<String, dynamic> json) => _VoucherDto(
id: json['id'] as String?,
name: json['name'] as String?,
description: json['description'] as String?,
imageUrl: json['image_url'] as String?,
voucherType: json['voucher_type'] as String?,
faceValue: (json['face_value'] as num?)?.toInt(),
pointCost: (json['point_cost'] as num?)?.toInt(),
maxPerCustomer: (json['max_per_customer'] as num?)?.toInt(),
validUntil: json['valid_until'] as String?,
available: (json['available'] as num?)?.toInt(),
);
Map<String, dynamic> _$VoucherDtoToJson(_VoucherDto instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'description': instance.description,
'image_url': instance.imageUrl,
'voucher_type': instance.voucherType,
'face_value': instance.faceValue,
'point_cost': instance.pointCost,
'max_per_customer': instance.maxPerCustomer,
'valid_until': instance.validUntil,
'available': instance.available,
};
_VoucherRedemptionDto _$VoucherRedemptionDtoFromJson(
Map<String, dynamic> json,
) => _VoucherRedemptionDto(
id: json['id'] as String?,
voucherId: json['voucher_id'] as String?,
voucherName: json['voucher_name'] as String?,
voucherImageUrl: json['voucher_image_url'] as String?,
voucherType: json['voucher_type'] as String?,
status: json['status'] as String?,
faceValue: (json['face_value'] as num?)?.toInt(),
pointCost: (json['point_cost'] as num?)?.toInt(),
code: json['code'] as String?,
codeExpiresAt: json['code_expires_at'] as String?,
completedAt: json['completed_at'] as String?,
createdAt: json['created_at'] as String?,
pointBalance: (json['point_balance'] as num?)?.toInt(),
replayed: json['replayed'] as bool?,
);
Map<String, dynamic> _$VoucherRedemptionDtoToJson(
_VoucherRedemptionDto instance,
) => <String, dynamic>{
'id': instance.id,
'voucher_id': instance.voucherId,
'voucher_name': instance.voucherName,
'voucher_image_url': instance.voucherImageUrl,
'voucher_type': instance.voucherType,
'status': instance.status,
'face_value': instance.faceValue,
'point_cost': instance.pointCost,
'code': instance.code,
'code_expires_at': instance.codeExpiresAt,
'completed_at': instance.completedAt,
'created_at': instance.createdAt,
'point_balance': instance.pointBalance,
'replayed': instance.replayed,
};
@@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import '../../common/api/interceptors/unauthorized_interceptor.dart';
import '../../domain/wallet/wallet.dart';
/// Hitung mundur bawaan bila server menjawab `429` tanpa header `Retry-After`.
@@ -8,7 +9,7 @@ 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();
if (isTokenRejected(response)) return const WalletFailure.unauthorized();
final body = response.data;
if (body is! Map) return _failureFromStatus(response);