feat: add payment
This commit is contained in:
+20
-1
@@ -1,5 +1,24 @@
|
||||
import 'package:flutter/services.dart' show appFlavor;
|
||||
|
||||
/// تنظیمات سراسری اپ.
|
||||
class AppConfig {
|
||||
/// فروشگاهِ فعال از روی `--flavor` که اپ با آن ساخته شده
|
||||
/// (`myket` | `bazaar`). اگر بدونِ flavor اجرا شود، `dev`.
|
||||
static String get store => appFlavor ?? 'dev';
|
||||
|
||||
/// کلیدِ عمومیِ RSA مایکت (برای تأییدِ محلیِ افزونهی myket_iap).
|
||||
static const String myketRsaKey = String.fromEnvironment(
|
||||
'MYKET_RSA',
|
||||
defaultValue:
|
||||
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVCtWUXVZfxULqP3QKlTqsXYkqg5qJYf6KnRSsyk7STB223Ji95IonwHrSrheffrPXi4othieBwl7rDaP0KnvkZ0Le0kr7xHCgTQgYeRuPq15HMm9mrFaXD3HgTF27f1/DcAN8z7eE02ShwAjd0LjYC2u2+T6z8iuQ2oh9LbJPnQIDAQAB',
|
||||
);
|
||||
|
||||
/// کلیدِ عمومیِ RSA کافهبازار (از پنلِ توسعهدهندهی بازار).
|
||||
static const String bazaarRsaKey = String.fromEnvironment(
|
||||
'BAZAAR_RSA',
|
||||
defaultValue: '',
|
||||
);
|
||||
|
||||
/// آدرس پایهی سرور.
|
||||
/// - موبایل واقعی روی همان Wi-Fi: IP محلی مک (پیشفرض فعلی).
|
||||
/// - امولاتور اندروید: `--dart-define=BASE_URL=http://10.0.2.2:8080`
|
||||
@@ -8,7 +27,7 @@ class AppConfig {
|
||||
/// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیشفرض را عوض کنید.
|
||||
static const String baseUrl = String.fromEnvironment(
|
||||
'BASE_URL',
|
||||
defaultValue: 'http://192.168.1.105:8080',
|
||||
defaultValue: 'https://hakem.approagency.ir',
|
||||
);
|
||||
|
||||
static String get apiUrl => '$baseUrl/api';
|
||||
|
||||
@@ -46,7 +46,12 @@ import '../../feature/wallet/domain/repository/wallet_repository.dart';
|
||||
import '../../feature/wallet/domain/use_cases/claim_daily_usecase.dart';
|
||||
import '../../feature/wallet/domain/use_cases/get_wallet_usecase.dart';
|
||||
import '../../feature/wallet/presentation/bloc/wallet_bloc.dart';
|
||||
import '../config.dart';
|
||||
import '../network/api_provider_imp.dart';
|
||||
import '../payment/bazaar_payment_gateway.dart';
|
||||
import '../payment/dev_payment_gateway.dart';
|
||||
import '../payment/myket_payment_gateway.dart';
|
||||
import '../payment/payment_gateway.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
|
||||
final GetIt locator = GetIt.instance;
|
||||
@@ -94,6 +99,9 @@ Future<void> setupLocator() async {
|
||||
locator.registerSingleton<SelectCardUseCase>(SelectCardUseCase(locator()));
|
||||
locator.registerSingleton<PurchaseUseCase>(PurchaseUseCase(locator()));
|
||||
locator.registerSingleton<AdRewardUseCase>(AdRewardUseCase(locator()));
|
||||
|
||||
// درگاهِ پرداخت بر اساسِ flavorِ فعال (مایکت/کافهبازار/توسعه).
|
||||
locator.registerSingleton<PaymentGateway>(_gatewayForStore(AppConfig.store));
|
||||
locator.registerSingleton<GetProfileUseCase>(GetProfileUseCase(locator()));
|
||||
locator.registerSingleton<SaveProfileUseCase>(SaveProfileUseCase(locator()));
|
||||
locator.registerSingleton<GetLeaderboardUseCase>(
|
||||
@@ -106,8 +114,8 @@ Future<void> setupLocator() async {
|
||||
locator.registerFactory<AuthBloc>(
|
||||
() => AuthBloc(locator(), locator(), locator(), locator()));
|
||||
locator.registerFactory<WalletBloc>(() => WalletBloc(locator(), locator()));
|
||||
locator.registerFactory<ShopBloc>(() =>
|
||||
ShopBloc(locator(), locator(), locator(), locator(), locator()));
|
||||
locator.registerFactory<ShopBloc>(() => ShopBloc(
|
||||
locator(), locator(), locator(), locator(), locator(), locator()));
|
||||
locator.registerFactory<ProfileBloc>(
|
||||
() => ProfileBloc(locator(), locator()));
|
||||
locator.registerFactory<LeaderboardBloc>(() => LeaderboardBloc(locator()));
|
||||
@@ -115,3 +123,16 @@ Future<void> setupLocator() async {
|
||||
locator.registerFactory<TierBloc>(() => TierBloc(locator()));
|
||||
locator.registerFactory<PrivateInfoBloc>(() => PrivateInfoBloc(locator()));
|
||||
}
|
||||
|
||||
/// درگاهِ پرداختِ متناظر با flavor. buildهای بدونِ flavor از درگاهِ توسعه
|
||||
/// استفاده میکنند (بکاند در DevFallback میپذیرد).
|
||||
PaymentGateway _gatewayForStore(String store) {
|
||||
switch (store) {
|
||||
case 'myket':
|
||||
return MyketPaymentGateway();
|
||||
case 'bazaar':
|
||||
return BazaarPaymentGateway();
|
||||
default:
|
||||
return DevPaymentGateway();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter_poolakey/flutter_poolakey.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import 'payment_gateway.dart';
|
||||
|
||||
/// پرداختِ درونبرنامهایِ کافهبازار (Poolakey) — flavorِ `bazaar`.
|
||||
class BazaarPaymentGateway implements PaymentGateway {
|
||||
bool _connected = false;
|
||||
|
||||
@override
|
||||
String get store => 'bazaar';
|
||||
|
||||
@override
|
||||
Future<void> connect() async {
|
||||
if (_connected) return;
|
||||
await FlutterPoolakey.connect(
|
||||
AppConfig.bazaarRsaKey,
|
||||
onDisconnected: () => _connected = false,
|
||||
);
|
||||
_connected = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PurchaseResult> purchase(String productSku) async {
|
||||
try {
|
||||
await connect();
|
||||
final info = await FlutterPoolakey.purchase(
|
||||
productSku,
|
||||
payload: 'hakemsho',
|
||||
);
|
||||
if (info.purchaseToken.isEmpty) {
|
||||
return const PurchaseResult.failure();
|
||||
}
|
||||
return PurchaseResult.success(
|
||||
token: info.purchaseToken,
|
||||
purchaseData: info.originalJson,
|
||||
signature: info.dataSignature,
|
||||
// کالای مصرفی باید مصرف شود تا خریدِ مجدد ممکن باشد.
|
||||
onConfirmed: () => FlutterPoolakey.consume(info.purchaseToken),
|
||||
);
|
||||
} catch (_) {
|
||||
// کنسلشدن یا خطای بازار ⇒ شکستِ خرید.
|
||||
return const PurchaseResult.failure();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'payment_gateway.dart';
|
||||
|
||||
/// درگاهِ توسعه: بدونِ اتصال به فروشگاه، یک توکنِ ساختگی میسازد. برای اجرای
|
||||
/// معمولیِ `flutter run` (بدونِ flavor) استفاده میشود؛ بکاند در حالتِ
|
||||
/// DevFallback آن را میپذیرد.
|
||||
class DevPaymentGateway implements PaymentGateway {
|
||||
@override
|
||||
String get store => 'dev';
|
||||
|
||||
@override
|
||||
Future<void> connect() async {}
|
||||
|
||||
@override
|
||||
Future<PurchaseResult> purchase(String productSku) async {
|
||||
final stamp = DateTime.now().millisecondsSinceEpoch;
|
||||
return PurchaseResult.success(
|
||||
token: 'dev-$productSku-$stamp',
|
||||
purchaseData: '{"productId":"$productSku","purchaseToken":"dev-$stamp"}',
|
||||
signature: 'dev',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:myket_iap/myket_iap.dart';
|
||||
import 'package:myket_iap/util/purchase.dart';
|
||||
|
||||
import '../config.dart';
|
||||
import 'payment_gateway.dart';
|
||||
|
||||
/// پرداختِ درونبرنامهایِ مایکت (flavorِ `myket`).
|
||||
class MyketPaymentGateway implements PaymentGateway {
|
||||
bool _inited = false;
|
||||
|
||||
@override
|
||||
String get store => 'myket';
|
||||
|
||||
@override
|
||||
Future<void> connect() async {
|
||||
if (_inited) return;
|
||||
await MyketIAP.init(rsaKey: AppConfig.myketRsaKey, enableDebugLogging: false);
|
||||
_inited = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PurchaseResult> purchase(String productSku) async {
|
||||
await connect();
|
||||
final Map<dynamic, dynamic> result =
|
||||
await MyketIAP.launchPurchaseFlow(sku: productSku, payload: 'hakemsho');
|
||||
|
||||
final Purchase? purchase = result[MyketIAP.PURCHASE] as Purchase?;
|
||||
if (purchase == null || purchase.mToken.isEmpty) {
|
||||
return const PurchaseResult.failure();
|
||||
}
|
||||
|
||||
return PurchaseResult.success(
|
||||
token: purchase.mToken,
|
||||
purchaseData: purchase.mOriginalJson,
|
||||
signature: purchase.mSignature,
|
||||
// کالای مصرفی (سکه/بلیت) باید مصرف شود تا کاربر بتواند دوباره بخرد.
|
||||
onConfirmed: () => MyketIAP.consume(purchase: purchase),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/// نتیجهی یک خریدِ فروشگاهی، مستقل از اینکه کدام SDK آن را تولید کرده.
|
||||
///
|
||||
/// بکاند برای تأییدِ آفلاینِ RSA به [purchaseData] (JSONِ خام) و [signature]
|
||||
/// نیاز دارد؛ [token] هم برای تأییدِ سمتسرور/سازگاری فرستاده میشود.
|
||||
class PurchaseResult {
|
||||
final bool success;
|
||||
final String token;
|
||||
final String purchaseData;
|
||||
final String signature;
|
||||
|
||||
/// پاکسازیِ لازم پس از تأییدِ بکاند (مثلاً مایکت باید خرید را consume کند).
|
||||
final Future<void> Function()? onConfirmed;
|
||||
|
||||
const PurchaseResult.success({
|
||||
required this.token,
|
||||
required this.purchaseData,
|
||||
required this.signature,
|
||||
this.onConfirmed,
|
||||
}) : success = true;
|
||||
|
||||
const PurchaseResult.failure()
|
||||
: success = false,
|
||||
token = '',
|
||||
purchaseData = '',
|
||||
signature = '',
|
||||
onConfirmed = null;
|
||||
}
|
||||
|
||||
/// انتزاعِ SDKِ پرداختِ درونبرنامهایِ مخصوصِ هر فروشگاه. هر flavor یک پیادهسازی
|
||||
/// ثبت میکند؛ بقیهی اپ از SDK بیخبر میماند.
|
||||
abstract class PaymentGateway {
|
||||
/// شناسهی فروشگاه که به بکاند فرستاده میشود (`myket` | `bazaar`).
|
||||
String get store;
|
||||
|
||||
/// جریانِ خریدِ [productSku] (همان UUIDِ محصول در پنلِ فروشگاه) را باز میکند.
|
||||
Future<PurchaseResult> purchase(String productSku);
|
||||
|
||||
/// اتصالِ اولیه (در صورتِ نیاز). پیش از اولین خرید صدا زده میشود.
|
||||
Future<void> connect() async {}
|
||||
}
|
||||
@@ -28,10 +28,14 @@ class PurchaseParams {
|
||||
final String kind;
|
||||
final String productId;
|
||||
final String token;
|
||||
final String purchaseData;
|
||||
final String signature;
|
||||
const PurchaseParams({
|
||||
required this.store,
|
||||
required this.kind,
|
||||
required this.productId,
|
||||
required this.token,
|
||||
this.purchaseData = '',
|
||||
this.signature = '',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user