membership_offer_controller.dart 9.43 KB
import 'package:doublefeel_flutter/app/utils/platform_compact.dart';
import 'package:doublefeel_flutter/core/network/api/pay_api.dart';
import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/data/models/pay/apple_pay_order_response.dart';
import 'package:doublefeel_flutter/data/models/pay/pay_models.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import 'package:get/get.dart';

import '../../../routes/app_pages.dart';

class MembershipOfferController extends GetxController {
  MembershipOfferController(this._vipApi, this._payApi);

  final VipApi _vipApi;
  final PayApi _payApi;

  final UserPreferencesStorage _userPreferences =
      Get.find<UserPreferencesStorage>();
  final AppPlatformHostApi _platformHostApi = AppPlatformHostApi();
  final AlipayHostApi _alipayHostApi = AlipayHostApi();

  final isUnlocking = false.obs;
  Future<void> unlock() async {
    if (isUnlocking.value) return;

    final productId = yearlyProduct.value?.id;

    try {
      if (isOhos()) {
        if (productId == null) {
          AppToast.show(l10n.purchaseProductInfoUnavailable);
          return;
        }
        isUnlocking.value = true;
        final orderResult = await _payApi.createCustomOrder(
          productId: productId,
          paymentChannel: PaymentChannel.alipay.value,
          productChannel: ProductChannel.huawei.value,
        );
        if (orderResult is! AppSuccess<ApplePayOrderResponse>) {
          if (isFromOnboard) {
            Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
          }
          return;
        }

        final prepayData = orderResult.data.prepayData?.trim();
        if (prepayData == null || prepayData.isEmpty) {
          AppToast.show(l10n.purchaseOrderInfoUnavailable);
          if (isFromOnboard) {
            Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
          }
          return;
        }

        //todo 鸿蒙Alipay prepayData
        final result = await _alipayHostApi.launchAliPay(prepayData);
        if (result == AliPayResultCode.success) {
          _trackSuccessPayOrder();
          await _completeSuccessfulPurchase();
        } else if (result == AliPayResultCode.error) {
          if (isFromOnboard) {
            Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
          }
        }
      } else if (isIOS()) {
        final appleProductId = yearlyProduct.value?.appleId;
        if (productId == null ||
            appleProductId == null ||
            appleProductId.isEmpty) {
          AppToast.show(l10n.purchaseProductInfoUnavailable);
          return;
        }
        isUnlocking.value = true;
        final orderResult = await _payApi.createOrderByApple(productId);
        if (orderResult is! AppSuccess<ApplePayOrderResponse>) {
          if (isFromOnboard) {
            Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
          }
          return;
        }

        final orderUuid = orderResult.data.orderInfo?.orderUuid?.trim();
        if (orderUuid == null || orderUuid.isEmpty) {
          AppToast.show(l10n.purchaseOrderInfoUnavailable);
          if (isFromOnboard) {
            Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
          }
          return;
        }

        final paymentResult = await _performApplePayment(
          productId: appleProductId,
          orderUuid: orderUuid,
        );
        await _handleApplePaymentResult(paymentResult);
      }
    } finally {
      isUnlocking.value = false;
    }
  }

  Future<void> _handleApplePaymentResult(
    AppleProductPaymentResult? result,
  ) async {
    if (result?.success == true) {
      _trackSuccessPayOrder();
      await _completeSuccessfulPurchase();
      return;
    }
    if (result?.success != false) return;

    AppToast.show(_applePaymentFailureMessage(result!));
    if (isFromOnboard) {
      Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
    }
  }

  void _trackSuccessPayOrder() {
    final trackMap = <String, dynamic>{};
    if (Get.arguments?['from'] == 'onboarding') {
      trackMap['channel_type'] = '新手引导';
    } else {
      trackMap['channel_type'] = Get.arguments?['channel_type'] ?? '';
    }
    trackMap['product_type'] = yearlyProduct.value?.appleId;
    trackMap['price'] = yearlyProduct.value?.price;
    if (isOhos()) {
      trackMap['currency_code'] = 'CN';
      trackMap['actual_price'] = yearlyProduct.value?.price;
    } else if (isIOS()) {
      final appleInfo = yearlyProductAppleInfo.value;
      trackMap['currency_code'] = appleInfo?.currencyCode ?? '';
      var actualPrice = appleInfo?.price ?? 0;
      trackMap['actual_price'] =
          actualPrice > 0 ? actualPrice : (appleInfo?.originPrice ?? 0);
    }

    ta.track(
      'success_doublefeel_pay_order',
      properties: trackMap,
    );
  }

  String _applePaymentFailureMessage(AppleProductPaymentResult result) {
    final errorCode = result.errorCode;
    if (errorCode != null) {
      return _localizedApplePaymentErrorCode(errorCode);
    }

    final errorMessage = _nonEmpty(result.errorMessage);
    if (errorMessage == null) return l10n.purchaseApplePaymentFailed;
    return _localizedApplePaymentErrorMessage(errorMessage);
  }

  String _localizedApplePaymentErrorCode(int errorCode) {
    return switch (errorCode) {
      -1 => l10n.purchaseApplePaymentInvalidOrder,
      -2 => l10n.purchaseApplePaymentProductNotFound,
      -3 => l10n.purchaseApplePaymentCancelled,
      -4 => l10n.purchaseApplePaymentVerificationFailed,
      -5 => l10n.purchaseApplePaymentFailed,
      _ => l10n.purchaseApplePaymentFailed,
    };
  }

  String _localizedApplePaymentErrorMessage(String errorMessage) {
    if (errorMessage == 'missingUUID') {
      return l10n.purchaseApplePaymentInvalidOrder;
    }
    if (errorMessage == 'productNotFound') {
      return l10n.purchaseApplePaymentProductNotFound;
    }
    if (errorMessage == 'userCancelled') {
      return l10n.purchaseApplePaymentCancelled;
    }
    if (errorMessage == 'failedVerification') {
      return l10n.purchaseApplePaymentVerificationFailed;
    }
    if (errorMessage == 'unknown') {
      return l10n.purchaseApplePaymentFailed;
    }
    return errorMessage;
  }

  Future<void> _completeSuccessfulPurchase() async {
    try {
      await _refreshVipInfo();
    } finally {
      if (isFromOnboard) {
        Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
      } else {
        Get.offNamed(Routes.PREMIUM_ACTIVATED);
      }
    }
  }

  Future<void> _refreshVipInfo() async {
    final result = await _vipApi.getVipInfo();
    if (result case AppSuccess(data: final vipInfo)) {
      await _userPreferences.updateVipInfo(
        UserPreferencesVipInfo.fromVipInfo(vipInfo),
      );
    }
  }

  Future<AppleProductPaymentResult?> _performApplePayment({
    required String productId,
    required String orderUuid,
  }) async {
    try {
      return await _platformHostApi.performApplePayment(productId, orderUuid);
    } catch (_) {
      return null;
    }
  }

  @override
  void onInit() {
    super.onInit();
    isFromOnboard = Get.arguments?['from'] == 'onboarding';
    // getProductList();
    _refreshVipInfo();
    getProductList();
  }

  var isFromOnboard = false;

  final yearlyProduct = Rxn<PayProduct>();
  final yearlyProductAppleInfo = Rxn<AppleProductInfo>();

  Future<void> getProductList() async {
    final result = await _payApi.getProductList();
    if (result is! AppSuccess<PayProductListResponse>) return;

    if (isIOS()) {
      final products = (result.data.productList ?? const <PayProduct>[])
          .where((product) => _nonEmpty(product.appleId) != null)
          .toList();
      yearlyProduct.value =
          products.firstWhereOrNull((p) => p.content?.isDiscountOffer == 1);
      if (yearlyProduct.value != null) {
        final appleProductId = _nonEmpty(yearlyProduct.value!.appleId);
        if (appleProductId == null) return;

        yearlyProductAppleInfo.value = await _requestAppleProductInfo(
            productId: appleProductId,
            baseUnit: yearlyProduct.value!.content?.baseUnit() ?? 1);
      }
    } else if (isOhos()) {
      final products = (result.data.productList ?? const <PayProduct>[]);
      yearlyProduct.value =
          products.firstWhereOrNull((p) => p.content?.isDiscountOffer == 1);
    }
  }

  Future<AppleProductInfo?> _requestAppleProductInfo({
    required String productId,
    required int baseUnit,
  }) async {
    try {
      return await _platformHostApi.requestAppleProductInfo(
        productId,
        baseUnit,
      );
    } catch (_) {
      return null;
    }
  }

  String? _nonEmpty(String? value) {
    final trimmed = value?.trim();
    if (trimmed == null || trimmed.isEmpty) return null;
    return trimmed;
  }

  void onBackPressed() {
    if (isFromOnboard) {
      Get.offAllNamed(AppRoutes.home, arguments: {'isFromOnboard': true});
    } else {
      Get.back();
    }
  }

  @override
  void onReady() {
    super.onReady();
    ta.track('enter_doublefeel_vip_discount_page');
  }
}