@jolibox/implement 1.2.4 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.rush/temp/package-deps_build.json +24 -18
  2. package/dist/common/cache/__tests__/request-cache-service.test.d.ts +1 -0
  3. package/dist/common/cache/request-cache-service.d.ts +111 -0
  4. package/dist/common/report/base-tracker.d.ts +2 -1
  5. package/dist/common/rewards/cached-fetch-reward.d.ts +46 -0
  6. package/dist/common/rewards/cached-reward-service.d.ts +24 -0
  7. package/dist/common/rewards/fetch-reward.d.ts +2 -3
  8. package/dist/common/rewards/index.d.ts +2 -0
  9. package/dist/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.d.ts +34 -0
  10. package/dist/common/rewards/registers/utils/coins/jolicoin/fetch-balance.d.ts +2 -1
  11. package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +34 -0
  12. package/dist/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.d.ts +2 -1
  13. package/dist/index.js +9 -9
  14. package/dist/index.native.js +33 -33
  15. package/dist/native/payment/payment-service.d.ts +36 -30
  16. package/implement.build.log +2 -2
  17. package/package.json +5 -5
  18. package/src/common/cache/__tests__/request-cache-service.test.ts +686 -0
  19. package/src/common/cache/request-cache-service.ts +393 -0
  20. package/src/common/report/base-tracker.ts +2 -2
  21. package/src/common/rewards/cached-fetch-reward.ts +241 -0
  22. package/src/common/rewards/cached-reward-service.ts +255 -0
  23. package/src/common/rewards/fetch-reward.ts +17 -93
  24. package/src/common/rewards/index.ts +3 -0
  25. package/src/common/rewards/registers/utils/coins/commands/use-payment.ts +8 -0
  26. package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +177 -0
  27. package/src/common/rewards/registers/utils/coins/jolicoin/fetch-balance.ts +13 -1
  28. package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +2 -0
  29. package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +181 -0
  30. package/src/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.ts +13 -1
  31. package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +2 -0
  32. package/src/h5/api/ads.ts +5 -0
  33. package/src/h5/api/storage.ts +2 -2
  34. package/src/h5/http/index.ts +2 -2
  35. package/src/h5/report/event-tracker.ts +2 -2
  36. package/src/native/api/ads.ts +7 -1
  37. package/src/native/api/payment.ts +4 -4
  38. package/src/native/payment/__tests__/payment-service-simple.test.ts +97 -31
  39. package/src/native/payment/payment-service.ts +224 -210
  40. package/src/native/rewards/ui/payment-modal.ts +14 -7
@@ -5,149 +5,81 @@ import { StandardResponse } from '@jolibox/types';
5
5
  import { applyNative } from '@jolibox/native-bridge';
6
6
  import { innerFetch as fetch } from '@/native/network';
7
7
  import type { PaymentResult } from './payment-helper';
8
+ import { RequestCacheService, RequestAdapter } from '@/common/cache/request-cache-service';
8
9
 
9
10
  type PaymentPurchaseType = 'JOLI_COIN' | 'JOLI_GEM';
10
11
 
11
- const CoinFetchUrlMap: Record<PaymentPurchaseType, string> = {
12
- JOLI_COIN: '/api/joli-coin/balance-detail',
13
- JOLI_GEM: '/api/joli-gem/balance-detail'
14
- };
12
+ // Request/Response interfaces for RequestCacheService
15
13
 
16
- // 缓存数据接口
17
- export interface CachedPaymentChoices {
18
- choices: IPaymentChoice[];
19
- timestamp: number;
20
- expiresAt: number;
21
- productIds: string[];
22
- }
14
+ type PaymentRequest = Record<string, never>;
23
15
 
24
- export class PaymentService {
25
- private productInfoCache: Record<PaymentPurchaseType, IPaymentChoice[]> = {
26
- JOLI_COIN: [],
27
- JOLI_GEM: []
28
- };
16
+ interface PaymentResponse {
17
+ balance: number;
18
+ enableAutoDeduct: boolean;
19
+ paymentChoices: IPaymentChoice[];
20
+ }
29
21
 
30
- // 新增:paymentChoices 缓存
31
- private static paymentChoicesCache = new Map<string, CachedPaymentChoices>();
32
- private static readonly PAYMENT_CHOICES_CACHE_DURATION = 10 * 60 * 1000; // 10分钟缓存
22
+ interface PaymentCacheData {
23
+ paymentChoices: IPaymentChoice[];
24
+ }
33
25
 
34
- async getJolicoinProductsInfo(type: PaymentPurchaseType) {
35
- if (this.productInfoCache[type].length > 0) {
36
- return this.productInfoCache[type];
37
- }
38
- const productsInfo = await PaymentService.getProductsInfo(CoinFetchUrlMap[type]);
39
- this.productInfoCache[type] = productsInfo?.paymentChoices ?? [];
40
- return this.productInfoCache[type];
41
- }
26
+ interface PaymentRealTimeData {
27
+ balance: number;
28
+ enableAutoDeduct: boolean;
29
+ }
42
30
 
43
- async purchase(type: PaymentPurchaseType, productId: string) {
44
- const productsInfo = await this.getJolicoinProductsInfo(type);
45
- const appStoreProductId = productsInfo.find(
46
- (choice) => choice.productId === productId
47
- )?.appStoreProductId;
48
- if (!appStoreProductId) {
49
- throw new Error('appStoreProductId not found');
31
+ // Utility function for merging response data
32
+ function mergeResponseData(
33
+ responseData: { paymentChoices: IPaymentChoice[] },
34
+ data: { [appStoreProductId: string]: { price: string } }
35
+ ) {
36
+ Object.keys(data).forEach((key) => {
37
+ const choice = responseData.paymentChoices.find((choice) => choice.appStoreProductId === key);
38
+ if (choice) {
39
+ choice.totalAmountStr = data[key].price;
50
40
  }
41
+ });
51
42
 
52
- const result = (await paymentHelper.invokePayment(
53
- type === 'JOLI_COIN' ? 'JOLI_COIN_IAP' : 'JOLI_GEM_IAP',
54
- {
55
- productId,
56
- appStoreProductId
57
- }
58
- )) as PaymentResult<{ totalAmount: string }>;
59
-
60
- return result;
61
- }
62
-
63
- private static mergeResponseData(
64
- responseData: { paymentChoices: IPaymentChoice[] },
65
- data: { [appStoreProductId: string]: { price: string } }
66
- ) {
67
- Object.keys(data).forEach((key) => {
68
- const choice = responseData.paymentChoices.find((choice) => choice.appStoreProductId === key);
69
- if (choice) {
70
- choice.totalAmountStr = data[key].price;
71
- }
72
- });
73
-
74
- responseData.paymentChoices = responseData.paymentChoices.filter(
75
- (choice) => !isUndefinedOrNull(choice.totalAmountStr)
76
- ) as IPaymentChoice[];
77
- }
78
-
79
- static async getProductsInfo(apiEndpoint: string): Promise<
80
- | {
81
- balance: number;
82
- enableAutoDeduct: boolean;
83
- paymentChoices: IPaymentChoice[];
84
- }
85
- | undefined
86
- > {
87
- return Promise.race([
88
- this.getProductsInfoInternal(apiEndpoint),
89
- new Promise<never>((_, reject) => {
90
- setTimeout(() => {
91
- reject(new Error('[PaymentService] Request timeout after 3 seconds'));
92
- }, 3000);
93
- })
94
- ]);
95
- }
43
+ responseData.paymentChoices = responseData.paymentChoices.filter(
44
+ (choice) => !isUndefinedOrNull(choice.totalAmountStr)
45
+ ) as IPaymentChoice[];
46
+ }
96
47
 
97
- private static async getProductsInfoInternal(apiEndpoint: string): Promise<
98
- | {
99
- balance: number;
100
- enableAutoDeduct: boolean;
101
- paymentChoices: IPaymentChoice[];
102
- }
103
- | undefined
104
- > {
105
- // 首先获取服务端数据(余额等实时数据)
106
- const { response } = await fetch<
107
- StandardResponse<{
108
- balance: number;
109
- enableAutoDeduct: boolean;
110
- paymentChoices: IPaymentChoice[];
111
- }>
112
- >(apiEndpoint, {
48
+ // Payment Request Adapter for RequestCacheService
49
+ class PaymentRequestAdapter
50
+ implements RequestAdapter<PaymentRequest, PaymentResponse, PaymentCacheData, PaymentRealTimeData>
51
+ {
52
+ // applyNative 的缓存
53
+ private static nativePriceCache = new Map<string, { [appStoreProductId: string]: { price: string } }>();
54
+ private static readonly NATIVE_PRICE_CACHE_DURATION = 30 * 60 * 1000; // 30分钟缓存
55
+ private static nativePriceCacheTimestamp = new Map<string, number>();
56
+
57
+ async execute(endpoint: string, _options?: PaymentRequest): Promise<PaymentResponse> {
58
+ // 获取服务端数据(余额等实时数据)
59
+ const { response } = await fetch<StandardResponse<PaymentResponse>>(endpoint, {
113
60
  method: 'GET',
114
61
  appendHostCookie: true,
115
62
  responseType: 'json'
116
63
  });
117
64
 
65
+ console.log('[PaymentService] get products info from server', endpoint, response.data);
66
+
118
67
  if (!response.data?.data) {
119
68
  throw new Error('get products info failed');
120
69
  }
121
70
 
122
71
  const serverData = response.data.data;
123
72
 
124
- // 检查 paymentChoices 缓存
125
- const cachedChoices = this.getPaymentChoicesFromCache(apiEndpoint, serverData.paymentChoices);
126
-
127
- if (cachedChoices) {
128
- console.log('[PaymentService] Using cached paymentChoices');
129
-
130
- // 使用缓存的 paymentChoices,但保持服务端的余额等实时数据
131
- return {
132
- balance: serverData.balance,
133
- enableAutoDeduct: serverData.enableAutoDeduct,
134
- paymentChoices: cachedChoices
135
- };
136
- }
137
-
138
- console.log('[PaymentService] Fetching fresh paymentChoices from native');
139
-
140
- // 缓存未命中,请求原生数据
141
- const { data } = await applyNative('requestProductDetailsAsync', {
142
- appStoreProductIds:
143
- serverData.paymentChoices
144
- ?.filter((choice) => typeof choice.appStoreProductId === 'string')
145
- .map((choice) => choice.appStoreProductId as string) ?? []
146
- });
73
+ const appStoreProductIds =
74
+ serverData.paymentChoices
75
+ ?.filter((choice) => typeof choice.appStoreProductId === 'string')
76
+ .map((choice) => choice.appStoreProductId as string) ?? [];
147
77
 
148
- if (data) {
149
- this.mergeResponseData(serverData, data);
150
- this.cachePaymentChoices(apiEndpoint, serverData.paymentChoices);
78
+ if (appStoreProductIds.length > 0) {
79
+ const nativeData = await this.getNativePriceData(appStoreProductIds);
80
+ if (nativeData) {
81
+ mergeResponseData(serverData, nativeData);
82
+ }
151
83
  }
152
84
 
153
85
  if (serverData.paymentChoices.length === 0) {
@@ -157,123 +89,205 @@ export class PaymentService {
157
89
  return serverData;
158
90
  }
159
91
 
160
- // read paymentChoices from cache
161
- private static getPaymentChoicesFromCache(
162
- apiEndpoint: string,
163
- serverChoices: IPaymentChoice[]
164
- ): IPaymentChoice[] | null {
165
- const cached = this.paymentChoicesCache.get(apiEndpoint);
166
-
167
- if (!cached) {
168
- return null;
92
+ private async getNativePriceData(
93
+ appStoreProductIds: string[]
94
+ ): Promise<{ [appStoreProductId: string]: { price: string } } | null> {
95
+ // 检查缓存
96
+ const cacheKey = appStoreProductIds.sort().join(',');
97
+ const cached = PaymentRequestAdapter.nativePriceCache.get(cacheKey);
98
+ const cacheTime = PaymentRequestAdapter.nativePriceCacheTimestamp.get(cacheKey) || 0;
99
+
100
+ if (cached && Date.now() - cacheTime < PaymentRequestAdapter.NATIVE_PRICE_CACHE_DURATION) {
101
+ console.log('[PaymentService] Using cached native price data');
102
+ return cached;
169
103
  }
170
104
 
171
- if (Date.now() > cached.expiresAt) {
172
- this.paymentChoicesCache.delete(apiEndpoint);
173
- return null;
174
- }
105
+ console.log('[PaymentService] Fetching fresh native price data');
106
+
107
+ try {
108
+ const { data } = await applyNative('requestProductDetailsAsync', {
109
+ appStoreProductIds
110
+ });
175
111
 
176
- const serverProductIds = this.extractProductIds(serverChoices);
177
- const cachedProductIds = cached.productIds;
112
+ if (data) {
113
+ // cache
114
+ PaymentRequestAdapter.nativePriceCache.set(cacheKey, data);
115
+ PaymentRequestAdapter.nativePriceCacheTimestamp.set(cacheKey, Date.now());
116
+ }
178
117
 
179
- if (!this.arraysEqual(serverProductIds, cachedProductIds)) {
180
- console.log('[PaymentService] Server paymentChoices changed, invalidating cache');
181
- this.paymentChoicesCache.delete(apiEndpoint);
182
- return null;
118
+ return data;
119
+ } catch (error) {
120
+ console.warn('[PaymentService] Failed to fetch native product details:', error);
121
+ throw error; // rethrow error, let upper layer handle it
183
122
  }
123
+ }
184
124
 
185
- const updatedChoices = this.updateChoicesWithServerData(cached.choices, serverChoices);
125
+ extractCacheableData(response: PaymentResponse): PaymentCacheData {
126
+ return {
127
+ paymentChoices: response.paymentChoices
128
+ };
129
+ }
186
130
 
187
- return updatedChoices;
131
+ extractRealTimeData?: undefined;
132
+ mergeData(cached: PaymentCacheData, realTime: PaymentRealTimeData): PaymentResponse {
133
+ return {
134
+ balance: realTime.balance,
135
+ enableAutoDeduct: realTime.enableAutoDeduct,
136
+ paymentChoices: cached.paymentChoices
137
+ };
188
138
  }
189
139
 
190
- private static cachePaymentChoices(apiEndpoint: string, choices: IPaymentChoice[]): void {
191
- this.paymentChoicesCache.set(apiEndpoint, {
192
- choices: JSON.parse(JSON.stringify(choices)), // deep copy
193
- timestamp: Date.now(),
194
- expiresAt: Date.now() + this.PAYMENT_CHOICES_CACHE_DURATION,
195
- productIds: this.extractProductIds(choices)
196
- });
140
+ processCachedData(cached: PaymentCacheData): PaymentResponse {
141
+ // when only cached data is available, use default real-time data values
142
+ return {
143
+ balance: 0, // default balance
144
+ enableAutoDeduct: false, // default not enable auto deduct
145
+ paymentChoices: cached.paymentChoices
146
+ };
147
+ }
197
148
 
198
- console.log(`[PaymentService] Cached ${choices.length} payment choices for ${apiEndpoint}`);
149
+ // clear native price cache static method
150
+ static clearNativePriceCache(): void {
151
+ this.nativePriceCache.clear();
152
+ this.nativePriceCacheTimestamp.clear();
153
+ console.log('[PaymentService] Cleared native price cache');
199
154
  }
155
+ }
200
156
 
201
- private static extractProductIds(choices: IPaymentChoice[]): string[] {
202
- return choices
203
- .map((choice) => choice.productId)
204
- .filter(Boolean)
205
- .sort();
157
+ // Base Payment Service class
158
+ class BasePaymentService extends RequestCacheService<
159
+ PaymentRequest,
160
+ PaymentResponse,
161
+ PaymentCacheData,
162
+ PaymentRealTimeData
163
+ > {
164
+ // 失败计数器,按 endpoint 统计
165
+ private static failureCounters = new Map<string, number>();
166
+ private static readonly MAX_FAILURE_COUNT = 2;
167
+
168
+ constructor(private apiEndpoint: string, private paymentType: PaymentPurchaseType) {
169
+ super(new PaymentRequestAdapter(), {
170
+ duration: 10 * 60 * 1000, // 10分钟缓存 paymentChoices
171
+ timeout: 1000 // 1秒超时
172
+ });
206
173
  }
207
174
 
208
- private static arraysEqual(arr1: string[], arr2: string[]): boolean {
209
- if (arr1.length !== arr2.length) {
210
- return false;
175
+ async getProductsInfo(): Promise<PaymentResponse | undefined> {
176
+ // 检查失败计数,如果达到最大失败次数则直接抛出错误
177
+ const currentFailureCount = BasePaymentService.failureCounters.get(this.apiEndpoint) || 0;
178
+ if (currentFailureCount >= BasePaymentService.MAX_FAILURE_COUNT) {
179
+ throw new Error(
180
+ `getProductsInfo has failed more than ${BasePaymentService.MAX_FAILURE_COUNT} times for ${this.apiEndpoint}`
181
+ );
211
182
  }
212
183
 
213
- return arr1.every((item, index) => item === arr2[index]);
184
+ try {
185
+ const result = await this.request(this.apiEndpoint);
186
+ // 成功时重置失败计数器
187
+ BasePaymentService.failureCounters.delete(this.apiEndpoint);
188
+ return result;
189
+ } catch (error) {
190
+ // 失败时增加计数器
191
+ const newCount = currentFailureCount + 1;
192
+ BasePaymentService.failureCounters.set(this.apiEndpoint, newCount);
193
+ console.warn(
194
+ `[PaymentService] getProductsInfo failed (${newCount}/${
195
+ BasePaymentService.MAX_FAILURE_COUNT + 1
196
+ }) for ${this.apiEndpoint}:`,
197
+ error
198
+ );
199
+ throw error;
200
+ }
214
201
  }
215
202
 
216
- private static updateChoicesWithServerData(
217
- cachedChoices: IPaymentChoice[],
218
- serverChoices: IPaymentChoice[]
219
- ): IPaymentChoice[] {
220
- const serverChoiceMap = new Map(serverChoices.map((choice) => [choice.productId, choice]));
221
-
222
- return cachedChoices.map((cachedChoice) => {
223
- const serverChoice = serverChoiceMap.get(cachedChoice.productId);
203
+ async purchase(productId: string): Promise<PaymentResult<{ totalAmount: string }>> {
204
+ const productsInfo = await this.getProductsInfo();
205
+ const appStoreProductId = productsInfo?.paymentChoices.find(
206
+ (choice) => choice.productId === productId
207
+ )?.appStoreProductId;
208
+ if (!appStoreProductId) {
209
+ throw new Error('appStoreProductId not found');
210
+ }
224
211
 
225
- if (serverChoice) {
226
- return {
227
- ...cachedChoice,
228
- quantity: serverChoice.quantity
229
- };
212
+ const result = (await paymentHelper.invokePayment(
213
+ this.paymentType === 'JOLI_COIN' ? 'JOLI_COIN_IAP' : 'JOLI_GEM_IAP',
214
+ {
215
+ productId,
216
+ appStoreProductId
230
217
  }
218
+ )) as PaymentResult<{ totalAmount: string }>;
231
219
 
232
- return cachedChoice;
233
- });
220
+ return result;
234
221
  }
235
222
 
236
- // clear paymentChoices cache
237
- public static clearPaymentChoicesCache(apiEndpoint?: string): void {
238
- if (apiEndpoint) {
239
- this.paymentChoicesCache.delete(apiEndpoint);
240
- console.log(`[PaymentService] Cleared cache for ${apiEndpoint}`);
241
- } else {
242
- this.paymentChoicesCache.clear();
243
- console.log('[PaymentService] Cleared all payment choices cache');
244
- }
223
+ // 获取产品信息(使用 RequestCacheService)
224
+ async getProductsInfoWithBalance(): Promise<PaymentResponse | undefined> {
225
+ return this.request(this.apiEndpoint);
245
226
  }
246
227
 
247
- public static getPaymentChoicesCacheStats(): {
248
- cacheCount: number;
249
- validCount: number;
250
- expiredCount: number;
251
- } {
252
- const now = Date.now();
253
- let validCount = 0;
254
- let expiredCount = 0;
255
-
256
- for (const cached of this.paymentChoicesCache.values()) {
257
- if (now > cached.expiresAt) {
258
- expiredCount++;
259
- } else {
260
- validCount++;
261
- }
262
- }
228
+ // 强制刷新产品信息
229
+ async refreshProductsInfo(): Promise<PaymentResponse | undefined> {
230
+ return this.forceRequest(this.apiEndpoint);
231
+ }
263
232
 
264
- return {
265
- cacheCount: this.paymentChoicesCache.size,
266
- validCount,
267
- expiredCount
268
- };
233
+ // 重置失败计数器(供测试使用)
234
+ static resetFailureCounters(): void {
235
+ this.failureCounters.clear();
269
236
  }
270
237
 
271
- public static clearExpiredPaymentChoicesCache(): void {
272
- const now = Date.now();
273
- for (const [key, cached] of this.paymentChoicesCache) {
274
- if (now > cached.expiresAt) {
275
- this.paymentChoicesCache.delete(key);
276
- }
277
- }
238
+ // 获取失败计数器状态(供测试使用)
239
+ static getFailureCount(endpoint: string): number {
240
+ return this.failureCounters.get(endpoint) || 0;
241
+ }
242
+ }
243
+
244
+ // JOLI_COIN payment service
245
+ export class JoliCoinPaymentService extends BasePaymentService {
246
+ constructor() {
247
+ super('/api/joli-coin/balance-detail', 'JOLI_COIN');
248
+ }
249
+ }
250
+
251
+ // JOLI_GEM payment service
252
+ export class JoliGemPaymentService extends BasePaymentService {
253
+ constructor() {
254
+ super('/api/joli-gem/balance-detail', 'JOLI_GEM');
278
255
  }
279
256
  }
257
+
258
+ // service instance management
259
+ let joliCoinServiceInstance: JoliCoinPaymentService | null = null;
260
+ let joliGemServiceInstance: JoliGemPaymentService | null = null;
261
+
262
+ // create JOLI_COIN payment service instance
263
+ export const createJoliCoinPaymentService = (): JoliCoinPaymentService => {
264
+ if (joliCoinServiceInstance) {
265
+ return joliCoinServiceInstance;
266
+ }
267
+ joliCoinServiceInstance = new JoliCoinPaymentService();
268
+ return joliCoinServiceInstance;
269
+ };
270
+
271
+ // create JOLI_GEM payment service instance
272
+ export const createJoliGemPaymentService = (): JoliGemPaymentService => {
273
+ if (joliGemServiceInstance) {
274
+ return joliGemServiceInstance;
275
+ }
276
+ joliGemServiceInstance = new JoliGemPaymentService();
277
+ return joliGemServiceInstance;
278
+ };
279
+
280
+ // 导出原生价格缓存清理方法供测试使用
281
+ export const clearNativePriceCache = (): void => {
282
+ PaymentRequestAdapter.clearNativePriceCache();
283
+ };
284
+
285
+ // 导出失败计数器重置方法供测试使用
286
+ export const resetFailureCounters = (): void => {
287
+ BasePaymentService.resetFailureCounters();
288
+ };
289
+
290
+ // 导出获取失败计数方法供测试使用
291
+ export const getFailureCount = (endpoint: string): number => {
292
+ return BasePaymentService.getFailureCount(endpoint);
293
+ };
@@ -28,7 +28,7 @@ import { track } from '@/native/report';
28
28
  import { updateAutoDeductConfig } from './utils';
29
29
  import { createEventPromiseHandler } from '@/common/rewards/registers/utils/event-listener';
30
30
  import { TrackEvent } from '@jolibox/types';
31
- import { PaymentService } from '@/native/payment/payment-service';
31
+ import { createJoliCoinPaymentService, createJoliGemPaymentService } from '@/native/payment/payment-service';
32
32
 
33
33
  // 货币配置映射
34
34
  interface CurrencyPaymentConfig {
@@ -85,6 +85,9 @@ const CURRENCY_PAYMENT_CONFIG: Record<'JOLI_COIN' | 'JOLI_GEM', CurrencyPaymentC
85
85
 
86
86
  const loading = createLoading();
87
87
 
88
+ const JoliCoinPaymentService = createJoliCoinPaymentService();
89
+ const JoliGemPaymentService = createJoliGemPaymentService();
90
+
88
91
  const modalUseFrequencyConfig = createEventPromiseHandler<
89
92
  IUseModalFrequencyConfig,
90
93
  typeof UseModalFrequencyEventName
@@ -114,6 +117,7 @@ rewardsEmitter.on(
114
117
  await loading.show({
115
118
  duration: 3000
116
119
  });
120
+
117
121
  const config = await modalUseFrequencyConfig.getData();
118
122
  const { canShow: canShowPaymentModal } = await paymentConfig.frequencyChecker(
119
123
  config.joliCoinUseAndCharge[paymentConfig.frequencyConfigKey]
@@ -134,7 +138,9 @@ rewardsEmitter.on(
134
138
  await loading.show({
135
139
  duration: 3000
136
140
  });
137
- const balenceDetails = await PaymentService.getProductsInfo(paymentConfig.apiEndpoint);
141
+ const paymentService = currencyType === 'JOLI_COIN' ? JoliCoinPaymentService : JoliGemPaymentService;
142
+ // get product info from cache, if not found, get from server and cache it
143
+ const balenceDetails = await paymentService.getProductsInfo();
138
144
  loading.hide();
139
145
 
140
146
  if (!balenceDetails) {
@@ -158,7 +164,7 @@ rewardsEmitter.on(
158
164
  ...choice,
159
165
  totalAmountStr: choice.totalAmountStr ?? ''
160
166
  })) ?? [],
161
- enableAutoDeduct: balenceDetails.enableAutoDeduct
167
+ enableAutoDeduct: params.enableAutoDeduct
162
168
  },
163
169
  buttons: {
164
170
  confirm: {
@@ -183,8 +189,8 @@ rewardsEmitter.on(
183
189
  return;
184
190
  }
185
191
 
186
- const balenceDetails = await PaymentService.getProductsInfo(paymentConfig.apiEndpoint);
187
- if ((balenceDetails?.balance ?? 0) >= params.quantity) {
192
+ const newBalence = await paymentService.refreshProductsInfo();
193
+ if ((newBalence?.balance ?? 0) >= params.quantity) {
188
194
  rewardsEmitter.emit(PaymentResultEventName, {
189
195
  paymentResult: 'SUCCESS',
190
196
  currency: currencyType
@@ -193,6 +199,7 @@ rewardsEmitter.on(
193
199
  return;
194
200
  }
195
201
  }
202
+ // invoke native payment
196
203
  console.log('invokeNativePayment', productId);
197
204
  const appStoreProductId = balenceDetails?.paymentChoices?.find(
198
205
  (choice) => choice.productId === productId
@@ -280,9 +287,9 @@ rewardsEmitter.on(
280
287
 
281
288
  /** preload payment details */
282
289
 
283
- PaymentService.getProductsInfo(CURRENCY_PAYMENT_CONFIG.JOLI_COIN.apiEndpoint).catch((e) => {
290
+ JoliCoinPaymentService.getProductsInfo().catch((e) => {
284
291
  console.error('preload joli coin payment details failed', e);
285
292
  });
286
- PaymentService.getProductsInfo(CURRENCY_PAYMENT_CONFIG.JOLI_GEM.apiEndpoint).catch((e) => {
293
+ JoliGemPaymentService.getProductsInfo().catch((e) => {
287
294
  console.error('preload joli gem payment details failed', e);
288
295
  });