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,35 +0,0 @@
|
||||
part of '../game.dart';
|
||||
|
||||
@freezed
|
||||
abstract class Game with _$Game {
|
||||
const Game._();
|
||||
|
||||
const factory Game({
|
||||
required String id,
|
||||
required String name,
|
||||
required String type,
|
||||
required bool isActive,
|
||||
required Map<String, dynamic> metadata,
|
||||
required List<GamePrize> prizes,
|
||||
required String createdAt,
|
||||
required String updatedAt,
|
||||
}) = _Game;
|
||||
|
||||
factory Game.empty() => const Game(
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
isActive: false,
|
||||
metadata: {},
|
||||
prizes: [],
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
);
|
||||
|
||||
/// Biaya sekali main dalam EnakCoin (`metadata.coin_cost`), default 1.
|
||||
int get coinCost {
|
||||
final value = metadata['coin_cost'];
|
||||
final cost = value is num ? value.toInt() : int.tryParse('$value');
|
||||
return cost == null || cost <= 0 ? 1 : cost;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
part of '../game.dart';
|
||||
|
||||
@freezed
|
||||
abstract class GamePrize with _$GamePrize {
|
||||
const factory GamePrize({
|
||||
required String id,
|
||||
required String gameId,
|
||||
required String name,
|
||||
required Map<String, dynamic> metadata,
|
||||
required String createdAt,
|
||||
required String updatedAt,
|
||||
}) = _GamePrize;
|
||||
|
||||
factory GamePrize.empty() => const GamePrize(
|
||||
id: '',
|
||||
gameId: '',
|
||||
name: '',
|
||||
metadata: {},
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
part of '../game.dart';
|
||||
|
||||
@freezed
|
||||
abstract class SpinResult with _$SpinResult {
|
||||
const factory SpinResult({
|
||||
required String gamePlayId,
|
||||
required int coinsUsed,
|
||||
required String prizeId,
|
||||
required String prizeName,
|
||||
required int coinsRemaining,
|
||||
}) = _SpinResult;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
part of '../game.dart';
|
||||
|
||||
@freezed
|
||||
sealed class GameFailure with _$GameFailure {
|
||||
const factory GameFailure.serverError(ApiFailure failure) = _ServerError;
|
||||
const factory GameFailure.unexpectedError() = _UnexpectedError;
|
||||
const factory GameFailure.dynamicErrorMessage(String erroMessage) =
|
||||
_DynamicErrorMessage;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../common/api/api_failure.dart';
|
||||
|
||||
part 'game.freezed.dart';
|
||||
|
||||
part 'entity/game_entity.dart';
|
||||
part 'entity/game_prize_entity.dart';
|
||||
part 'entity/spin_result_entity.dart';
|
||||
part 'failures/game_failure.dart';
|
||||
part 'repositories/i_game_repository.dart';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
part of '../game.dart';
|
||||
|
||||
abstract class IGameRepository {
|
||||
Future<Either<GameFailure, Game>> ferrisWheel();
|
||||
|
||||
/// Main satu kali; memotong EnakCoin sebesar biaya game. Tanpa PIN.
|
||||
Future<Either<GameFailure, SpinResult>> spin({required String gameId});
|
||||
}
|
||||
@@ -71,18 +71,11 @@ abstract class CustomerOrderItem with _$CustomerOrderItem {
|
||||
|
||||
@freezed
|
||||
abstract class CustomerOrderPayment with _$CustomerOrderPayment {
|
||||
const CustomerOrderPayment._();
|
||||
|
||||
const factory CustomerOrderPayment({
|
||||
required String id,
|
||||
required String methodName,
|
||||
required String methodType,
|
||||
required int amount,
|
||||
required int refundAmount,
|
||||
|
||||
/// Hanya pada pembayaran EnakPoint.
|
||||
int? pointsUsed,
|
||||
}) = _CustomerOrderPayment;
|
||||
|
||||
bool get isPoint => methodType == 'point';
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ abstract class Outlet with _$Outlet {
|
||||
required String name,
|
||||
String? address,
|
||||
|
||||
/// Kasir di outlet ini menerima pembayaran EnakPoint.
|
||||
required bool acceptsPointPayment,
|
||||
|
||||
/// Belanja di outlet ini memberi EnakPoint.
|
||||
required bool earnsPoints,
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
part of '../wallet.dart';
|
||||
|
||||
@freezed
|
||||
abstract class PaymentCode with _$PaymentCode {
|
||||
const factory PaymentCode({
|
||||
required String code,
|
||||
required String qrPayload,
|
||||
required DateTime expiresAt,
|
||||
}) = _PaymentCode;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
part of '../wallet.dart';
|
||||
|
||||
/// Voucher di katalog yang bisa ditukar dengan EnakPoint sekarang (§9.1).
|
||||
@freezed
|
||||
abstract class Voucher with _$Voucher {
|
||||
const Voucher._();
|
||||
|
||||
const factory Voucher({
|
||||
required String id,
|
||||
required String name,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
required VoucherType type,
|
||||
|
||||
/// Nilai voucher dalam rupiah.
|
||||
required int faceValue,
|
||||
|
||||
/// EnakPoint yang dipotong saat ditukar.
|
||||
required int pointCost,
|
||||
|
||||
/// Batas tukar per customer; `null` berarti tanpa batas.
|
||||
int? maxPerCustomer,
|
||||
DateTime? validUntil,
|
||||
|
||||
/// Sisa stok; `null` berarti stok tidak dihitung.
|
||||
int? available,
|
||||
}) = _Voucher;
|
||||
|
||||
bool get isOutOfStock => available != null && available! <= 0;
|
||||
}
|
||||
|
||||
/// Voucher hasil tukar milik customer (§9.3).
|
||||
@freezed
|
||||
abstract class VoucherRedemption with _$VoucherRedemption {
|
||||
const factory VoucherRedemption({
|
||||
required String id,
|
||||
required String voucherId,
|
||||
required String voucherName,
|
||||
String? voucherImageUrl,
|
||||
required VoucherType voucherType,
|
||||
required VoucherRedemptionStatus status,
|
||||
required int faceValue,
|
||||
required int pointCost,
|
||||
|
||||
/// `null` bila voucher tidak memakai kode; customer menunjukkan layar
|
||||
/// voucher ke kasir.
|
||||
String? code,
|
||||
DateTime? codeExpiresAt,
|
||||
DateTime? completedAt,
|
||||
DateTime? createdAt,
|
||||
}) = _VoucherRedemption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class VoucherRedemptionPage with _$VoucherRedemptionPage {
|
||||
const factory VoucherRedemptionPage({
|
||||
required List<VoucherRedemption> items,
|
||||
required int page,
|
||||
required int totalPages,
|
||||
}) = _VoucherRedemptionPage;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class VoucherRedeemResult with _$VoucherRedeemResult {
|
||||
const factory VoucherRedeemResult({
|
||||
required VoucherRedemption redemption,
|
||||
|
||||
/// Saldo EnakPoint setelah ditukar.
|
||||
required int pointBalance,
|
||||
required bool replayed,
|
||||
}) = _VoucherRedeemResult;
|
||||
}
|
||||
@@ -13,29 +13,47 @@ enum WalletCurrency {
|
||||
value == coin.apiValue ? coin : point;
|
||||
}
|
||||
|
||||
enum WalletTransactionType {
|
||||
earn('EARN', 'Dari belanja'),
|
||||
earnReversal('EARN_REVERSAL', 'Dibatalkan (order di-void/refund)'),
|
||||
payment('PAYMENT', 'Bayar pesanan'),
|
||||
paymentRefund('PAYMENT_REFUND', 'Pengembalian pembayaran'),
|
||||
exchangeOut('EXCHANGE_OUT', 'Ditukar ke EnakPoint'),
|
||||
exchangeIn('EXCHANGE_IN', 'Hasil tukar EnakCoin'),
|
||||
transferOut('TRANSFER_OUT', 'Transfer keluar'),
|
||||
transferIn('TRANSFER_IN', 'Transfer masuk'),
|
||||
gameSpend('GAME_SPEND', 'Main game'),
|
||||
expire('EXPIRE', 'Kedaluwarsa'),
|
||||
adjustment('ADJUSTMENT', 'Koreksi'),
|
||||
migration('MIGRATION', 'Saldo awal'),
|
||||
unknown('', 'Transaksi');
|
||||
const _bothCurrencies = [WalletCurrency.point, WalletCurrency.coin];
|
||||
|
||||
const WalletTransactionType(this.apiValue, this.label);
|
||||
enum WalletTransactionType {
|
||||
earn('EARN', 'Dari belanja', _bothCurrencies),
|
||||
earnReversal(
|
||||
'EARN_REVERSAL',
|
||||
'Dibatalkan (order di-void/refund)',
|
||||
_bothCurrencies,
|
||||
),
|
||||
exchangeOut('EXCHANGE_OUT', 'Ditukar ke EnakPoint', [WalletCurrency.coin]),
|
||||
exchangeIn('EXCHANGE_IN', 'Hasil tukar EnakCoin', [WalletCurrency.point]),
|
||||
transferOut('TRANSFER_OUT', 'Transfer keluar', _bothCurrencies),
|
||||
transferIn('TRANSFER_IN', 'Transfer masuk', _bothCurrencies),
|
||||
gameSpend('GAME_SPEND', 'Main game', [WalletCurrency.coin]),
|
||||
gameSpendRefund('GAME_SPEND_REFUND', 'Biaya main dikembalikan', [
|
||||
WalletCurrency.coin,
|
||||
]),
|
||||
gameReward('GAME_REWARD', 'Hadiah game', [WalletCurrency.coin]),
|
||||
rewardRedeem('REWARD_REDEEM', 'Ditukar ke voucher', [WalletCurrency.point]),
|
||||
rewardRedeemRefund('REWARD_REDEEM_REFUND', 'Penukaran voucher dibatalkan', [
|
||||
WalletCurrency.point,
|
||||
]),
|
||||
expire('EXPIRE', 'Kedaluwarsa', _bothCurrencies),
|
||||
adjustment('ADJUSTMENT', 'Koreksi', _bothCurrencies),
|
||||
migration('MIGRATION', 'Saldo awal', _bothCurrencies),
|
||||
|
||||
/// Tipe baru dari backend yang belum dikenal app: tampilkan `description`
|
||||
/// tanpa label.
|
||||
unknown('', '', []);
|
||||
|
||||
const WalletTransactionType(this.apiValue, this.label, this.currencies);
|
||||
|
||||
final String apiValue;
|
||||
final String label;
|
||||
|
||||
/// Tipe yang bisa dipilih di filter riwayat.
|
||||
static List<WalletTransactionType> get filterable =>
|
||||
values.where((e) => e != unknown).toList();
|
||||
/// Mata uang yang bisa memiliki tipe ini.
|
||||
final List<WalletCurrency> currencies;
|
||||
|
||||
/// Tipe yang bisa dipilih di filter riwayat untuk tab [currency].
|
||||
static List<WalletTransactionType> filterableFor(WalletCurrency currency) =>
|
||||
values.where((e) => e.currencies.contains(currency)).toList();
|
||||
|
||||
static WalletTransactionType fromApi(String? value) =>
|
||||
values.firstWhere((e) => e.apiValue == value, orElse: () => unknown);
|
||||
@@ -49,3 +67,36 @@ enum PinOtpPurpose {
|
||||
|
||||
final String apiValue;
|
||||
}
|
||||
|
||||
enum VoucherType {
|
||||
fixedValue('FIXED_VALUE'),
|
||||
percentage('PERCENTAGE'),
|
||||
freeItem('FREE_ITEM'),
|
||||
merchantBenefit('MERCHANT_BENEFIT'),
|
||||
unknown('');
|
||||
|
||||
const VoucherType(this.apiValue);
|
||||
|
||||
final String apiValue;
|
||||
|
||||
static VoucherType fromApi(String? value) =>
|
||||
values.firstWhere((e) => e.apiValue == value, orElse: () => unknown);
|
||||
}
|
||||
|
||||
enum VoucherRedemptionStatus {
|
||||
completed('COMPLETED'),
|
||||
|
||||
/// Sedang diproses penyedia luar; EnakPoint sudah terpotong.
|
||||
pending('PENDING'),
|
||||
|
||||
/// Gagal; EnakPoint dikembalikan otomatis (`REWARD_REDEEM_REFUND`).
|
||||
failed('FAILED'),
|
||||
unknown('');
|
||||
|
||||
const VoucherRedemptionStatus(this.apiValue);
|
||||
|
||||
final String apiValue;
|
||||
|
||||
static VoucherRedemptionStatus fromApi(String? value) =>
|
||||
values.firstWhere((e) => e.apiValue == value, orElse: () => unknown);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of '../wallet.dart';
|
||||
|
||||
/// Riwayat order customer. App hanya melihat order; tidak membuat atau
|
||||
/// membayarnya (§7).
|
||||
/// membayarnya; EnakPoint tidak bisa dipakai membayar (§1).
|
||||
abstract class IOrderRepository {
|
||||
Future<Either<WalletFailure, CustomerOrderPage>> getOrders({
|
||||
required int page,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
part of '../wallet.dart';
|
||||
|
||||
/// Tukar EnakPoint ke voucher (§9).
|
||||
abstract class IVoucherRepository {
|
||||
/// Voucher yang aktif, dalam masa berlaku, dan masih ada stoknya.
|
||||
Future<Either<WalletFailure, List<Voucher>>> getVouchers();
|
||||
|
||||
Future<Either<WalletFailure, VoucherRedeemResult>> redeem({
|
||||
required String voucherId,
|
||||
required String pin,
|
||||
required String idempotencyKey,
|
||||
});
|
||||
|
||||
/// Penukaran milik customer, terbaru di atas.
|
||||
Future<Either<WalletFailure, VoucherRedemptionPage>> getRedemptions({
|
||||
required int page,
|
||||
int limit = 20,
|
||||
});
|
||||
}
|
||||
@@ -14,10 +14,6 @@ abstract class IWalletRepository {
|
||||
|
||||
Future<Either<WalletFailure, WalletExpiring>> getExpiring();
|
||||
|
||||
Future<Either<WalletFailure, PaymentCode>> createPaymentCode({
|
||||
required String pin,
|
||||
});
|
||||
|
||||
Future<Either<WalletFailure, ExchangePreview>> previewExchange({
|
||||
required int coins,
|
||||
});
|
||||
|
||||
@@ -9,15 +9,16 @@ part 'entities/wallet_enum.dart';
|
||||
part 'entities/wallet_summary_entity.dart';
|
||||
part 'entities/wallet_transaction_entity.dart';
|
||||
part 'entities/wallet_expiring_entity.dart';
|
||||
part 'entities/payment_code_entity.dart';
|
||||
part 'entities/exchange_entity.dart';
|
||||
part 'entities/transfer_entity.dart';
|
||||
part 'entities/pin_entity.dart';
|
||||
part 'entities/outlet_entity.dart';
|
||||
part 'entities/customer_order_entity.dart';
|
||||
part 'entities/voucher_entity.dart';
|
||||
part 'failures/wallet_failure.dart';
|
||||
part 'repositories/i_wallet_repository.dart';
|
||||
part 'repositories/i_pin_repository.dart';
|
||||
part 'repositories/i_device_repository.dart';
|
||||
part 'repositories/i_outlet_repository.dart';
|
||||
part 'repositories/i_order_repository.dart';
|
||||
part 'repositories/i_voucher_repository.dart';
|
||||
|
||||
+1206
-320
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user