feat: integrate enakgame and voucher
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user