123 lines
4.5 KiB
Dart
123 lines
4.5 KiB
Dart
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
import '../../../../core/payment/payment_gateway.dart';
|
|
import '../../../../core/storage/token_storage.dart';
|
|
import '../../../../core/resources/data_state.dart';
|
|
import '../../../../core/usecase/use_case.dart';
|
|
import '../../domain/use_cases/ad_reward_usecase.dart';
|
|
import '../../domain/use_cases/buy_card_usecase.dart';
|
|
import '../../domain/use_cases/get_shop_usecase.dart';
|
|
import '../../domain/use_cases/purchase_usecase.dart';
|
|
import '../../domain/use_cases/select_card_usecase.dart';
|
|
import 'shop_event.dart';
|
|
import 'shop_state.dart';
|
|
import 'shop_status.dart';
|
|
|
|
class ShopBloc extends Bloc<ShopEvent, ShopBlocState> {
|
|
final GetShopUseCase getShopUseCase;
|
|
final BuyCardUseCase buyCardUseCase;
|
|
final SelectCardUseCase selectCardUseCase;
|
|
final PurchaseUseCase purchaseUseCase;
|
|
final AdRewardUseCase adRewardUseCase;
|
|
final PaymentGateway gateway;
|
|
final TokenStorage storage;
|
|
|
|
ShopBloc(
|
|
this.getShopUseCase,
|
|
this.buyCardUseCase,
|
|
this.selectCardUseCase,
|
|
this.purchaseUseCase,
|
|
this.adRewardUseCase,
|
|
this.gateway,
|
|
this.storage,
|
|
) : super(ShopBlocState.initial()) {
|
|
on<LoadShopEvent>((event, emit) => _load(emit));
|
|
|
|
on<BuyCardEvent>((event, emit) =>
|
|
_action(emit, () => buyCardUseCase(event.cardId)));
|
|
|
|
on<SelectCardEvent>((event, emit) =>
|
|
_action(emit, () => selectCardUseCase(event.cardId)));
|
|
|
|
on<PurchaseEvent>((event, emit) => _action(emit, () => _purchase(event)));
|
|
|
|
on<AdRewardEvent>((event, emit) => _action(
|
|
emit,
|
|
() async {
|
|
final res = await adRewardUseCase(
|
|
'dev-ad-${DateTime.now().millisecondsSinceEpoch}');
|
|
if (res is DataSuccess) {
|
|
return const DataSuccess('سکه رایگان دریافت شد');
|
|
}
|
|
return DataError<String>(res.error!);
|
|
},
|
|
));
|
|
}
|
|
|
|
/// جریانِ خرید: ابتدا پرداختِ فروشگاهی (native) اجرا میشود، سپس مدرکِ خرید
|
|
/// (token/purchaseData/signature) برای اعتبارسنجی به بکاند فرستاده میشود و
|
|
/// در پایان، خرید در فروشگاه consume میشود.
|
|
Future<DataState<String>> _purchase(PurchaseEvent event) async {
|
|
// برای پرداختِ native به SKUِ فروشگاه نیاز است؛ اگر تنظیم نشده باشد از
|
|
// شناسهی کاتالوگ استفاده میکنیم (حالتِ توسعه/تکفروشگاهی).
|
|
final sku = event.sku.isNotEmpty ? event.sku : event.productId;
|
|
|
|
// شمارهی موبایل را بهعنوان payload میفرستیم تا خرید در پنلِ فروشگاه
|
|
// قابلِ ردیابی باشد.
|
|
final mobile = await storage.readMobile() ?? '';
|
|
|
|
final PurchaseResult result;
|
|
try {
|
|
result = await gateway.purchase(sku, payload: mobile);
|
|
} catch (e) {
|
|
return DataError<String>('خطا در پرداخت: $e');
|
|
}
|
|
if (!result.success) {
|
|
return DataError<String>('خرید انجام نشد یا لغو شد');
|
|
}
|
|
|
|
final verify = await purchaseUseCase(PurchaseParams(
|
|
store: gateway.store,
|
|
kind: event.kind,
|
|
productId: event.productId,
|
|
token: result.token,
|
|
purchaseData: result.purchaseData,
|
|
signature: result.signature,
|
|
));
|
|
|
|
// پس از تأییدِ بکاند، خرید را در فروشگاه نهایی/مصرف میکنیم.
|
|
if (verify is DataSuccess && result.onConfirmed != null) {
|
|
try {
|
|
await result.onConfirmed!();
|
|
} catch (_) {/* consume ناموفق حیاتی نیست */}
|
|
}
|
|
return verify;
|
|
}
|
|
|
|
Future<void> _load(Emitter<ShopBlocState> emit) async {
|
|
emit(state.copyWith(loadStatus: ShopLoading()));
|
|
final res = await getShopUseCase(const NoParams());
|
|
if (res is DataSuccess) {
|
|
emit(state.copyWith(loadStatus: ShopLoaded(res.data!)));
|
|
} else {
|
|
emit(state.copyWith(loadStatus: ShopLoadError(res.error!)));
|
|
}
|
|
}
|
|
|
|
/// اجرای یک عملیات، نمایش وضعیت و سپس بازخوانی کاتالوگ.
|
|
Future<void> _action(
|
|
Emitter<ShopBlocState> emit,
|
|
Future<DataState<String>> Function() action,
|
|
) async {
|
|
if (state.busy) return;
|
|
emit(state.copyWith(actionStatus: ActionLoading()));
|
|
final res = await action();
|
|
if (res is DataSuccess) {
|
|
emit(state.copyWith(actionStatus: ActionSuccess(res.data!)));
|
|
await _load(emit);
|
|
} else {
|
|
emit(state.copyWith(actionStatus: ActionError(res.error!)));
|
|
}
|
|
}
|
|
}
|