import { rewardsEmitter, PaymentResultEventName, InvokePaymentEventName, IInvokePaymentEvent, IPaymentChoice, UseModalFrequencyEventName, IUseModalFrequencyConfig } from '@/common/rewards/reward-emitter'; import { createPaymentJolicoinModal, createToast } from '@jolibox/ui'; import { innerFetch as fetch } from '@/native/network'; import { StandardResponse } from '@jolibox/types'; import { context } from '@/common/context'; import { login } from '@/native/api/login'; import { EventType } from '@jolibox/common'; import { checkPaymentFrequency, updatePaymentFrequency, checkPaymentFrequencyGem, updatePaymentFrequencyGem } from '../check-frequency'; import { paymentHelper } from '@/native/payment'; import { createLoading } from '@jolibox/ui'; import { canIUseNative } from '@/native/api/base'; import { track } from '@/native/report'; import { updateAutoDeductConfig } from './utils'; import { createEventPromiseHandler } from '@/common/rewards/registers/utils/event-listener'; import { TrackEvent } from '@jolibox/types'; import { createJoliCoinPaymentService, createJoliGemPaymentService } from '@/native/payment/payment-service'; // 货币配置映射 interface CurrencyPaymentConfig { frequencyConfigKey: keyof IUseModalFrequencyConfig['joliCoinUseAndCharge']; trackEvents: { modalShow: TrackEvent; orderClick: TrackEvent; payEnsure: TrackEvent; payResult: TrackEvent; adUnlock: TrackEvent; }; frequencyChecker: (config: { dailyMaxPopUps?: number; minInterval?: number; }) => Promise<{ canShow: boolean; isFirst: boolean }>; frequencyUpdater: () => Promise; apiEndpoint: string; paymentType: 'JOLI_COIN_IAP' | 'JOLI_GEM_IAP'; apiEndpointAutoDeduct: string; } const CURRENCY_PAYMENT_CONFIG: Record<'JOLI_COIN' | 'JOLI_GEM', CurrencyPaymentConfig> = { JOLI_COIN: { frequencyConfigKey: 'charge', trackEvents: { modalShow: 'coinorder_show', orderClick: 'coinorder_click', payEnsure: 'order_pay_ensure', payResult: 'order_pay_result', adUnlock: 'ad_unlock_click' }, frequencyChecker: checkPaymentFrequency, frequencyUpdater: updatePaymentFrequency, apiEndpoint: '/api/joli-coin/balance-detail', apiEndpointAutoDeduct: '/api/joli-coin/user-config', paymentType: 'JOLI_COIN_IAP' }, JOLI_GEM: { frequencyConfigKey: 'charge', // 暂时复用,可后续扩展 trackEvents: { modalShow: 'gem_order_show', orderClick: 'gem_order_click', payEnsure: 'gem_pay_ensure', payResult: 'gem_pay_result', adUnlock: 'gem_ad_unlock_click' }, frequencyChecker: checkPaymentFrequencyGem, frequencyUpdater: updatePaymentFrequencyGem, apiEndpoint: '/api/joli-gem/balance-detail', apiEndpointAutoDeduct: '/api/joli-gem/user-config', paymentType: 'JOLI_GEM_IAP' } }; const loading = createLoading(); const JoliCoinPaymentService = createJoliCoinPaymentService(); const JoliGemPaymentService = createJoliGemPaymentService(); const modalUseFrequencyConfig = createEventPromiseHandler< IUseModalFrequencyConfig, typeof UseModalFrequencyEventName >(rewardsEmitter, UseModalFrequencyEventName); /** * payment jolicoin modal */ rewardsEmitter.on( InvokePaymentEventName, async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN' | 'JOLI_GEM' | 'ADS-JOLI_GEM', params: IInvokePaymentEvent) => { try { // 获取货币类型配置 const currencyType = params.currency; const paymentConfig = CURRENCY_PAYMENT_CONFIG[currencyType]; // TODO: temp remove it for dev if (!canIUseNative('requestPaymentSync:paymentBody:appStoreProductId')) { //TODO: show Toast console.info('requestPaymentSync:paymentBody:appStoreProductId not supported'); setTimeout(() => { rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED', currency: currencyType }); }, 0); return; } // handle showup frequecy if (type.includes('ADS-')) { await loading.show({ duration: 3000 }); const config = await modalUseFrequencyConfig.getData(); const { canShow: canShowPaymentModal } = await paymentConfig.frequencyChecker( config.joliCoinUseAndCharge[paymentConfig.frequencyConfigKey] ); console.log(`${currencyType} payment show by frequency`, canShowPaymentModal); loading.hide(); if (!canShowPaymentModal) { // return by frequency control rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED', currency: currencyType }); return; } console.log('show by frequency'); } /** * TODO: need merge totalAmountStr from native */ await loading.show({ duration: 3000 }); const paymentService = currencyType === 'JOLI_COIN' ? JoliCoinPaymentService : JoliGemPaymentService; // get product info from cache, if not found, get from server and cache it const balenceDetails = await paymentService.getProductsInfo(); loading.hide(); if (!balenceDetails || !balenceDetails.paymentChoices?.length) { rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED', currency: currencyType }); return; } track(paymentConfig.trackEvents.modalShow, { targetType: 'game', eventType: EventType.View, currency: currencyType }); const modal = createPaymentJolicoinModal({ data: { userCurrency: params.userCurrency, quantity: params.quantity, currency: currencyType, paymentChoices: balenceDetails.paymentChoices?.map((choice) => ({ ...choice, totalAmountStr: choice.totalAmountStr ?? '' })) ?? [], enableAutoDeduct: params.enableAutoDeduct }, buttons: { confirm: { text: params.confirmButtonText, onPress: async (productId: string) => { track(paymentConfig.trackEvents.payEnsure, { targetType: 'game', eventType: EventType.Click, payWay: 'app_iap', coinAmount: params.quantity, currency: currencyType, orderPrice: balenceDetails.paymentChoices.find((choice) => choice.productId === productId) ?.totalAmountStr ?? '' }); if (!context.hostUserInfo?.isLogin) { const { data } = await login({ skipLogin: true }); if (!data?.isLogin) { console.log('login failed'); return; } const newBalence = await paymentService.refreshProductsInfo(); if ((newBalence?.balance ?? 0) >= params.quantity) { rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS', currency: currencyType }); modal.destroy(); return; } } // invoke native payment console.log('invokeNativePayment', productId); const appStoreProductId = balenceDetails?.paymentChoices?.find( (choice) => choice.productId === productId )?.appStoreProductId; if (!appStoreProductId) { rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED', currency: currencyType }); modal.destroy(); return; } await loading.show({ duration: 3000 }); const { code } = await paymentHelper.invokePayment(paymentConfig.paymentType, { productId, appStoreProductId }); loading.hide(); track(paymentConfig.trackEvents.payResult, { eventType: EventType.Other, targetType: 'game', payWay: 'app_iap', coinAmount: params.quantity, currency: currencyType, orderPrice: balenceDetails.paymentChoices.find((choice) => choice.productId === productId) ?.totalAmountStr ?? '', payResult: code }); if (code !== 'SUCCESS') { /** add timeout for google panel closed */ console.info('[JoliboxSDK] payment failed in payment.invokePaymet'); return; } createToast(`{slot-success} {slot-i18n-jolicoin.unlockSuccess}`, { position: 'center', duration: 3000 }); rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS', currency: currencyType }); modal.destroy(); } }, cancel: { text: params.cancelButtonText, onPress: ({ type }) => { rewardsEmitter.emit(PaymentResultEventName, { paymentResult: (type ?? '') === 'CANCEL' ? 'CANCEL' : 'FAILED', currency: currencyType }); track(paymentConfig.trackEvents.adUnlock, { targetType: 'game', eventType: EventType.Click, currency: currencyType }); modal.destroy(); } }, onEnableDeductChanged: async (enabled: boolean) => { await updateAutoDeductConfig(enabled, paymentConfig.apiEndpointAutoDeduct); } }, onSelect: (productId: string) => { track(paymentConfig.trackEvents.orderClick, { targetType: 'game', eventType: EventType.Click, coinAmount: params.quantity, currency: currencyType, orderPrice: balenceDetails.paymentChoices.find((choice) => choice.productId === productId) ?.totalAmountStr ?? '' }); } }); await paymentConfig.frequencyUpdater(); } catch (error) { console.error('payment failed', error); rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED', currency: params.currency }); } } ); /** preload payment details */ JoliCoinPaymentService.getProductsInfo().catch((e) => { console.error('preload joli coin payment details failed', e); }); JoliGemPaymentService.getProductsInfo().catch((e) => { console.error('preload joli gem payment details failed', e); });