@jolibox/implement 1.2.7 → 1.2.9-beta.9

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 (66) hide show
  1. package/.rush/temp/package-deps_build.json +38 -28
  2. package/dist/common/rewards/registers/use-gem-only.d.ts +2 -1
  3. package/dist/common/rewards/registers/use-gem.d.ts +2 -1
  4. package/dist/common/rewards/registers/use-jolicoin-only.d.ts +2 -1
  5. package/dist/common/rewards/registers/use-jolicoin.d.ts +2 -1
  6. package/dist/common/rewards/registers/use-subscription.d.ts +2 -1
  7. package/dist/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.d.ts +1 -0
  8. package/dist/common/rewards/registers/utils/coins/joligem/gem-handler.d.ts +2 -1
  9. package/dist/common/rewards/registers/utils/common.d.ts +1 -0
  10. package/dist/common/rewards/registers/utils/subscription/sub-handler.d.ts +2 -1
  11. package/dist/common/rewards/reward-emitter.d.ts +8 -0
  12. package/dist/common/rewards/type.d.ts +1 -1
  13. package/dist/common/utils/index.d.ts +1 -1
  14. package/dist/index.js +25 -25
  15. package/dist/index.native.js +54 -54
  16. package/dist/native/payment/utils/__tests__/cache-with-storage.test.d.ts +1 -0
  17. package/dist/native/payment/utils/cache-with-storage.d.ts +7 -0
  18. package/dist/native/rewards/check-frequency.d.ts +8 -0
  19. package/dist/native/rewards/index.d.ts +1 -0
  20. package/dist/native/rewards/ui/subscription-modal.d.ts +1 -0
  21. package/dist/native/subscription/index.d.ts +3 -0
  22. package/dist/native/subscription/registers/base.d.ts +22 -0
  23. package/dist/native/subscription/registers/sub-app.d.ts +21 -0
  24. package/dist/native/subscription/registers/type.d.ts +10 -0
  25. package/dist/native/subscription/subscription-helper.d.ts +22 -0
  26. package/dist/native/subscription/subscription-service.d.ts +43 -0
  27. package/dist/native/subscription/type.d.ts +18 -0
  28. package/implement.build.log +2 -2
  29. package/package.json +5 -5
  30. package/src/common/context/index.ts +4 -1
  31. package/src/common/rewards/cached-fetch-reward.ts +16 -1
  32. package/src/common/rewards/registers/use-gem-only.ts +5 -2
  33. package/src/common/rewards/registers/use-gem.ts +5 -2
  34. package/src/common/rewards/registers/use-jolicoin-only.ts +5 -2
  35. package/src/common/rewards/registers/use-jolicoin.ts +5 -2
  36. package/src/common/rewards/registers/use-subscription.ts +5 -2
  37. package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +33 -11
  38. package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +34 -13
  39. package/src/common/rewards/registers/utils/common.ts +9 -0
  40. package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +16 -0
  41. package/src/common/rewards/registers/utils/subscription/sub-handler.ts +23 -7
  42. package/src/common/rewards/reward-emitter.ts +8 -0
  43. package/src/common/rewards/type.ts +1 -1
  44. package/src/common/utils/index.ts +1 -1
  45. package/src/h5/api/ads.ts +6 -3
  46. package/src/h5/bootstrap/auth/__tests__/auth.test.ts +15 -9
  47. package/src/h5/bootstrap/auth/sub.ts +1 -1
  48. package/src/native/api/ads.ts +43 -6
  49. package/src/native/api/call-host-method.ts +5 -61
  50. package/src/native/api/login.ts +22 -7
  51. package/src/native/api/payment.ts +78 -3
  52. package/src/native/payment/__tests__/payment-service-simple.test.ts +14 -1
  53. package/src/native/payment/payment-service.ts +26 -27
  54. package/src/native/payment/utils/__tests__/cache-with-storage.test.ts +414 -0
  55. package/src/native/payment/utils/cache-with-storage.ts +112 -0
  56. package/src/native/rewards/check-frequency.ts +6 -0
  57. package/src/native/rewards/index.ts +1 -0
  58. package/src/native/rewards/ui/payment-modal.ts +2 -2
  59. package/src/native/rewards/ui/subscription-modal.ts +81 -0
  60. package/src/native/subscription/index.ts +12 -0
  61. package/src/native/subscription/registers/base.ts +88 -0
  62. package/src/native/subscription/registers/sub-app.ts +258 -0
  63. package/src/native/subscription/registers/type.ts +13 -0
  64. package/src/native/subscription/subscription-helper.ts +53 -0
  65. package/src/native/subscription/subscription-service.ts +339 -0
  66. package/src/native/subscription/type.ts +18 -0
@@ -7,18 +7,28 @@ import { RewardsCommandType } from '@jolibox/types';
7
7
  import { createShowSubscriptionModal } from './commands/use-subscription';
8
8
  import { registerUseSubscriptionCommand } from './commands';
9
9
  import { context } from '@/common/context';
10
+ import { checkIfSupportSubscription } from '../common';
11
+ import { refreshBalanceCache } from '../coins/jolicoin/cached-fetch-balance';
12
+ import { refreshGemBalanceCache } from '../coins/joligem/cached-fetch-gem-balance';
10
13
 
11
14
  export const createCommonSubscriptionHandler = (
12
15
  type: 'SUBSCRIPTION',
13
16
  httpClient: IHttpClient,
14
17
  {
15
- handlers: { handleSubSuccess, handleSubFailed, unlockOptionsHandler, showSubscriptionModal }
18
+ handlers: {
19
+ handleSubSuccess,
20
+ handleSubFailed,
21
+ unlockOptionsHandler,
22
+ showSubscriptionModal,
23
+ hasSubscription
24
+ }
16
25
  }: {
17
26
  handlers: {
18
27
  handleSubSuccess?: () => void;
19
28
  handleSubFailed?: (params: IAdBreakParams) => void;
20
29
  unlockOptionsHandler: EventPromiseHandler<IUnlockOptionsEvent, typeof UnlockOptionsEventName>;
21
30
  showSubscriptionModal: ReturnType<typeof createShowSubscriptionModal>;
31
+ hasSubscription?: () => boolean;
22
32
  };
23
33
  }
24
34
  ) => {
@@ -31,6 +41,12 @@ export const createCommonSubscriptionHandler = (
31
41
 
32
42
  return async (params: IAdBreakParams) => {
33
43
  try {
44
+ const unlockOptions = await unlockOptionsHandler.getData();
45
+ const supportSubscription = checkIfSupportSubscription(unlockOptions, hasSubscription);
46
+ if (!supportSubscription) {
47
+ // pass to next handler
48
+ return false;
49
+ }
34
50
  let result = true;
35
51
  for (const command of commands) {
36
52
  const commandResult = await rewardsCommands.executeCommand(command as RewardsCommandType);
@@ -40,12 +56,11 @@ export const createCommonSubscriptionHandler = (
40
56
  }
41
57
  }
42
58
 
43
- if (!result) {
44
- handleSubFailed?.(params);
45
- return false;
46
- }
59
+ // refresh balance and gem balance cache.in case of user login, the balance and gem balance is not updated.
60
+ refreshBalanceCache(httpClient);
61
+ refreshGemBalanceCache(httpClient);
47
62
 
48
- console.log('-----unlockWithSubscription result-----');
63
+ console.log('-----unlockWithSubscription result-----', result);
49
64
  if (result) {
50
65
  try {
51
66
  params.adBreakDone?.({
@@ -82,7 +97,8 @@ export const createCommonSubscriptionHandler = (
82
97
  handleSubFailed?.(params);
83
98
  return false;
84
99
  } finally {
85
- unlockOptionsHandler.clearCache();
100
+ // do not clear cache for unlockoptions
101
+ // unlockOptionsHandler.clearCache();
86
102
  }
87
103
  };
88
104
  };
@@ -84,6 +84,10 @@ export interface IUseModalFrequencyConfig {
84
84
  dailyMaxPopUps?: number;
85
85
  minInterval?: number;
86
86
  };
87
+ joinMember: {
88
+ dailyMaxPopUps?: number;
89
+ minInterval?: number;
90
+ };
87
91
  };
88
92
  loginGuide: {
89
93
  show: boolean;
@@ -146,6 +150,10 @@ export const DefaltJoliCoinUseAndCharge = {
146
150
  useJolicoin: {
147
151
  dailyMaxPopUps: 2,
148
152
  minInterval: 8 * 60 * 60 * 1000
153
+ },
154
+ joinMember: {
155
+ dailyMaxPopUps: 2,
156
+ minInterval: 8 * 60 * 60 * 1000
149
157
  }
150
158
  };
151
159
 
@@ -8,7 +8,7 @@ export interface IGem {
8
8
  enableAutoDeduct: boolean;
9
9
  }
10
10
 
11
- export type IUnlockOptionType = 'JOLI_COIN' | 'ADS' | 'JOLI_GEM';
11
+ export type IUnlockOptionType = 'JOLI_COIN' | 'ADS' | 'JOLI_GEM' | 'SUBSCRIPTION';
12
12
 
13
13
  interface IJoliCoinChoice {
14
14
  joliCoinQuantity: number;
@@ -78,7 +78,7 @@ const ON_JOLIBOX_JOLI_COIN_USE_RESULT = 'ON_JOLIBOX_JOLI_COIN_USE_RESULT';
78
78
  const ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT = 'ON_JOLIBOX_JOLI_UNLOGIN_MODAL_RESULT_EVENT';
79
79
 
80
80
  // subscription
81
- const ON_GET_USER_SUB_STATUS = 'ON_GET_USER_SUB_STATUS';
81
+ const ON_GET_USER_SUB_STATUS = 'ON_JOLIBOX_GET_USER_SUB_STATUS';
82
82
  const ON_JOLIBOX_SUB_RESULT_EVENT = 'ON_JOLIBOX_SUB_RESULT_EVENT';
83
83
 
84
84
  interface ReceivedJoliboxCustomEvent {
package/src/h5/api/ads.ts CHANGED
@@ -150,21 +150,24 @@ rewardsHelper.registerRewardHandler('ADS', createAdsRewardHandler(adsHandler.get
150
150
  rewardsHelper.registerRewardHandler(
151
151
  'JOLI_COIN',
152
152
  createJolicoinRewardHandler(httpClient, {
153
- onUnlockSuccess: handleUnlockSuccess.bind(this)
153
+ onUnlockSuccess: handleUnlockSuccess.bind(this),
154
+ hasSubscription: () => true
154
155
  }) as unknown as (params?: unknown) => Promise<boolean>
155
156
  );
156
157
 
157
158
  rewardsHelper.registerRewardHandler(
158
159
  'JOLI_COIN_ONLY',
159
160
  createJolicoinOnlyRewardHandler(httpClient, {
160
- onUnlockSuccess: handleUnlockSuccess.bind(this)
161
+ onUnlockSuccess: handleUnlockSuccess.bind(this),
162
+ hasSubscription: () => true
161
163
  }) as unknown as (params?: unknown) => Promise<boolean>
162
164
  );
163
165
 
164
166
  rewardsHelper.registerRewardHandler(
165
167
  'SUBSCRIPTION',
166
168
  createSubscriptionRewardHandler(httpClient, {
167
- onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this)
169
+ onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this),
170
+ hasSubscription: () => true
168
171
  }) as unknown as (params?: unknown) => Promise<boolean>
169
172
  );
170
173
 
@@ -49,7 +49,7 @@ describe('getUserSubStatus', () => {
49
49
  it('should resolve with correct value when event is received', async () => {
50
50
  let eventHandler: ((data: EventData) => void) | null = null;
51
51
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
52
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
52
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
53
53
  eventHandler = handler;
54
54
  }
55
55
  return jest.fn();
@@ -79,7 +79,7 @@ describe('getUserSubStatus', () => {
79
79
  it('should resolve with false when isSubUser is false', async () => {
80
80
  let eventHandler: ((data: EventData) => void) | null = null;
81
81
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
82
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
82
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
83
83
  eventHandler = handler;
84
84
  }
85
85
  return jest.fn();
@@ -102,6 +102,8 @@ describe('getUserSubStatus', () => {
102
102
  });
103
103
 
104
104
  it('should reject on timeout', async () => {
105
+ jest.useFakeTimers();
106
+
105
107
  mockOnCustomEvent.mockImplementation(() => jest.fn());
106
108
  mockUuidv4.mockReturnValue('timeout-test-id');
107
109
 
@@ -111,12 +113,14 @@ describe('getUserSubStatus', () => {
111
113
  jest.advanceTimersByTime(3000);
112
114
 
113
115
  await expect(promise).rejects.toThrow('Timeout waiting for user sub status response');
116
+
117
+ jest.useRealTimers();
114
118
  });
115
119
 
116
120
  it('should initialize event listener only once', async () => {
117
121
  let eventHandler: ((data: EventData) => void) | null = null;
118
122
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
119
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
123
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
120
124
  eventHandler = handler;
121
125
  }
122
126
  return jest.fn();
@@ -133,7 +137,7 @@ describe('getUserSubStatus', () => {
133
137
 
134
138
  // Should only initialize listener once
135
139
  expect(mockOnCustomEvent).toHaveBeenCalledTimes(1);
136
- expect(mockOnCustomEvent).toHaveBeenCalledWith('ON_GET_USER_SUB_STATUS', expect.any(Function));
140
+ expect(mockOnCustomEvent).toHaveBeenCalledWith('ON_JOLIBOX_GET_USER_SUB_STATUS', expect.any(Function));
137
141
 
138
142
  // Should send notification for both calls
139
143
  expect(mockNotifyCustomEvent).toHaveBeenCalledTimes(2);
@@ -152,7 +156,7 @@ describe('getUserSubStatus', () => {
152
156
  it('should ignore responses with different sequenceId', async () => {
153
157
  let eventHandler: ((data: EventData) => void) | null = null;
154
158
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
155
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
159
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
156
160
  eventHandler = handler;
157
161
  }
158
162
  return jest.fn();
@@ -201,7 +205,7 @@ describe('getUserSubStatus', () => {
201
205
  it('should handle multiple concurrent requests with different sequenceIds', async () => {
202
206
  let eventHandler: ((data: EventData) => void) | null = null;
203
207
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
204
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
208
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
205
209
  eventHandler = handler;
206
210
  }
207
211
  return jest.fn();
@@ -248,7 +252,7 @@ describe('getUserSubStatus', () => {
248
252
  it('should clean up timeout when response is received', async () => {
249
253
  let eventHandler: ((data: EventData) => void) | null = null;
250
254
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
251
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
255
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
252
256
  eventHandler = handler;
253
257
  }
254
258
  return jest.fn();
@@ -278,9 +282,11 @@ describe('getUserSubStatus', () => {
278
282
  });
279
283
 
280
284
  it('should not resolve after timeout even if response comes later', async () => {
285
+ jest.useFakeTimers();
286
+
281
287
  let eventHandler: ((data: EventData) => void) | null = null;
282
288
  mockOnCustomEvent.mockImplementation((eventName: string, handler: (data: EventData) => void) => {
283
- if (eventName === 'ON_GET_USER_SUB_STATUS') {
289
+ if (eventName === 'ON_JOLIBOX_GET_USER_SUB_STATUS') {
284
290
  eventHandler = handler;
285
291
  }
286
292
  return jest.fn();
@@ -303,6 +309,6 @@ describe('getUserSubStatus', () => {
303
309
  });
304
310
  }
305
311
 
306
- // Promise should already be rejected, no additional effects
312
+ jest.useRealTimers();
307
313
  });
308
314
  });
@@ -14,7 +14,7 @@ let isListenerInitialized = false;
14
14
  const initializeEventListener = () => {
15
15
  if (isListenerInitialized) return;
16
16
 
17
- onCustomEvent('ON_GET_USER_SUB_STATUS', (data) => {
17
+ onCustomEvent('ON_JOLIBOX_GET_USER_SUB_STATUS', (data) => {
18
18
  const pendingRequest = pendingRequests.get(data.sequenceId);
19
19
  if (pendingRequest) {
20
20
  clearTimeout(pendingRequest.timeoutId);
@@ -1,5 +1,5 @@
1
1
  import { createCommands } from '@jolibox/common';
2
- import { createSyncAPI, registerCanIUse } from './base';
2
+ import { canIUseNative, createSyncAPI, registerCanIUse } from './base';
3
3
  import { createToast } from '@jolibox/ui';
4
4
  import '../rewards';
5
5
 
@@ -27,7 +27,8 @@ import {
27
27
  createGemRewardHandler,
28
28
  createGemOnlyRewardHandler,
29
29
  warmupGemBalanceCache,
30
- warmupBalanceCache
30
+ warmupBalanceCache,
31
+ createSubscriptionRewardHandler
31
32
  } from '@/common/rewards';
32
33
  import { adEventEmitter } from '@/common/ads';
33
34
  import { warmupRewardCache } from '@/common/rewards/fetch-reward';
@@ -107,7 +108,8 @@ rewardsHelper.registerRewardHandler(
107
108
  },
108
109
  onUnlockFailed: () => {
109
110
  console.log('onUnlockFailed');
110
- }
111
+ },
112
+ hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
111
113
  }) as unknown as (params?: unknown) => Promise<boolean>
112
114
  );
113
115
 
@@ -123,7 +125,8 @@ rewardsHelper.registerRewardHandler(
123
125
  },
124
126
  onUnlockFailed: () => {
125
127
  console.log('onUnlockFailed');
126
- }
128
+ },
129
+ hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
127
130
  }) as unknown as (params?: unknown) => Promise<boolean>
128
131
  );
129
132
 
@@ -136,7 +139,8 @@ rewardsHelper.registerRewardHandler(
136
139
  track('gem_unlock_success', {
137
140
  quantity: params.quantity
138
141
  });
139
- }
142
+ },
143
+ hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
140
144
  }) as unknown as (params?: unknown) => Promise<boolean>
141
145
  );
142
146
 
@@ -149,7 +153,20 @@ rewardsHelper.registerRewardHandler(
149
153
  quantity: params.quantity,
150
154
  isOnly: true
151
155
  });
152
- }
156
+ },
157
+ hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
158
+ }) as unknown as (params?: unknown) => Promise<boolean>
159
+ );
160
+
161
+ const handleUnlockWithSubscriptionSuccess = () => {
162
+ track('subscription_unlock_success', {});
163
+ };
164
+
165
+ rewardsHelper.registerRewardHandler(
166
+ 'SUBSCRIPTION',
167
+ createSubscriptionRewardHandler(httpClient, {
168
+ onSubSuccess: handleUnlockWithSubscriptionSuccess.bind(this),
169
+ hasSubscription: () => canIUseNative('invokeSubscriptionPanelSync')
153
170
  }) as unknown as (params?: unknown) => Promise<boolean>
154
171
  );
155
172
 
@@ -217,8 +234,28 @@ const adConfig = createSyncAPI('adConfig', {
217
234
  });
218
235
 
219
236
  let rewardLocked = false;
237
+
238
+ const wrapAdBreadDoneViewed = (params: IAdBreakParams) => {
239
+ params.adBreakDone?.({
240
+ breakType: params.type,
241
+ breakName: 'name' in params ? params.name ?? '' : '',
242
+ breakFormat: params.type === 'reward' ? 'reward' : 'interstitial',
243
+ breakStatus: 'viewed'
244
+ });
245
+ if ('adViewed' in params) {
246
+ params.adViewed?.();
247
+ }
248
+ };
249
+
220
250
  const adBreak = createSyncAPI('adBreak', {
221
251
  implement: (params: IAdBreakParams) => {
252
+ // check is sub user
253
+ const isSubUser = context.hostUserInfo?.isSubUser;
254
+ if (isSubUser) {
255
+ wrapAdBreadDoneViewed(params);
256
+ return;
257
+ }
258
+
222
259
  if (params.type === 'reward') {
223
260
  if (rewardLocked) {
224
261
  return;
@@ -1,65 +1,10 @@
1
- import { createAPI, registerCanIUse, t } from './base';
1
+ import { canIUseNative, createAPI, registerCanIUse, t } from './base';
2
2
  import { invokeNative } from '@jolibox/native-bridge';
3
- import { hostEmitter, UserCustomError } from '@jolibox/common';
3
+ import { UserCustomError } from '@jolibox/common';
4
4
  import { createCommands } from '@jolibox/common';
5
5
  import { context } from '@/common/context';
6
6
 
7
7
  const commands = createCommands();
8
- let supportedMethods: string[] = [];
9
- let isInitialized = false;
10
-
11
- const formatSupportedMethods = (
12
- methods: (
13
- | string
14
- | {
15
- method: string;
16
- platforms?: string[];
17
- nativeVersionCode?: number;
18
- }
19
- )[]
20
- ) => {
21
- const platform = context.platform;
22
- const nativeVersionCode = context.sdkInfo.nativeSDKVersionCode ?? 0;
23
- return methods.reduce((acc, method) => {
24
- if (typeof method === 'string') {
25
- acc.push(method);
26
- } else {
27
- const { method: platformMethod, platforms, nativeVersionCode: methodNativeVersionCode } = method;
28
- let canUse = true;
29
- if (platforms && !platforms.includes(platform)) {
30
- canUse = false;
31
- }
32
- if (methodNativeVersionCode && methodNativeVersionCode > nativeVersionCode) {
33
- canUse = false;
34
- }
35
- if (canUse) {
36
- acc.push(platformMethod);
37
- }
38
- }
39
- return acc;
40
- }, [] as string[]);
41
- };
42
-
43
- hostEmitter.on('onGlobalConfigChanged', (params) => {
44
- supportedMethods = formatSupportedMethods(params.globalConfig?.supportedHostMethods ?? []);
45
- isInitialized = true;
46
- });
47
-
48
- const getSupportedMethods = (): Promise<string[]> => {
49
- return new Promise((resolve) => {
50
- if (isInitialized) {
51
- resolve(supportedMethods);
52
- } else {
53
- const handler = (params: any) => {
54
- supportedMethods = formatSupportedMethods(params.globalConfig.supportedHostMethods ?? []);
55
- isInitialized = true;
56
- hostEmitter.off('onGlobalConfigChanged', handler);
57
- resolve(supportedMethods);
58
- };
59
- hostEmitter.on('onGlobalConfigChanged', handler);
60
- }
61
- });
62
- };
63
8
 
64
9
  const callHostMethodAsync = createAPI('callHostMethod', {
65
10
  paramsSchema: t.tuple(
@@ -69,10 +14,10 @@ const callHostMethodAsync = createAPI('callHostMethod', {
69
14
  })
70
15
  ),
71
16
  implement: async ({ method, params }) => {
72
- const supportedMethods = await getSupportedMethods();
73
- if (!supportedMethods.includes(method)) {
17
+ if (!canIUseNative(method)) {
74
18
  throw new UserCustomError('Method not supported', 10001);
75
19
  }
20
+
76
21
  const { errNo, errMsg, data } = await invokeNative('callHostMethodAsync', {
77
22
  method,
78
23
  params: {
@@ -106,8 +51,7 @@ const userTrackAsync = createAPI('userTrackAsync', {
106
51
  })
107
52
  ),
108
53
  implement: async ({ event, eventType, params }) => {
109
- const supportedMethods = await getSupportedMethods();
110
- if (!supportedMethods.includes('userTrackAsync')) {
54
+ if (!canIUseNative('userTrackAsync')) {
111
55
  throw new UserCustomError('Method not supported', 10001);
112
56
  }
113
57
  const { errNo, errMsg } = await invokeNative('trackAsync', {
@@ -19,19 +19,27 @@ registerCanIUse('login', {
19
19
  registerCanIUse('checkSession', {
20
20
  version: '1.0.0',
21
21
  success: {
22
- errMsg: '1.0.0'
22
+ errMsg: '1.0.0',
23
+ isLogin: '1.0.0',
24
+ isSubUser: '1.2.9'
23
25
  }
24
26
  });
25
27
 
26
28
  const loginDeferredMap = new Map<
27
29
  string,
28
- Deferred<{ isLogin: boolean; token?: string; isFirstLogin?: boolean; extra?: LoginExtra }>
30
+ Deferred<{
31
+ isLogin: boolean;
32
+ token?: string;
33
+ isFirstLogin?: boolean;
34
+ extra?: LoginExtra;
35
+ appAccountToken?: string;
36
+ }>
29
37
  >();
30
38
 
31
- onNative('onLoginStateChange', ({ isLogin, token, uuid, isFirstLogin, extra }) => {
39
+ onNative('onLoginStateChange', ({ isLogin, token, uuid, isFirstLogin, extra, appAccountToken }) => {
32
40
  const deferred = loginDeferredMap.get(uuid);
33
41
  if (deferred) {
34
- deferred.resolve({ isLogin, token, isFirstLogin, extra });
42
+ deferred.resolve({ isLogin, token, isFirstLogin, extra, appAccountToken });
35
43
  loginDeferredMap.delete(uuid);
36
44
  }
37
45
  });
@@ -81,12 +89,16 @@ export const loginImplement = async (
81
89
  isLogin: boolean;
82
90
  token?: string;
83
91
  isFirstLogin?: boolean;
92
+ appAccountToken?: string;
84
93
  extra?: LoginExtra;
85
94
  }>();
86
95
  loginDeferredMap.set(loginUUID, deferred);
87
96
  const loginRes = await deferred.promise;
88
- context.onEnvConfigChanged({ hostUserInfo: loginRes });
89
- const { extra, ...rest } = loginRes;
97
+ // update sub user status
98
+ const env = invokeNative('envSync');
99
+ const isSubUser = env?.data?.hostUserInfo?.isSubUser;
100
+ context.onEnvConfigChanged({ hostUserInfo: { ...loginRes, isSubUser } });
101
+ const { extra, appAccountToken, ...rest } = loginRes;
90
102
  hostEmitter.emit('onLoginComplete', rest);
91
103
  return needExtra ? loginRes : rest;
92
104
  };
@@ -110,8 +122,11 @@ const checkSession = createAPI('checkSession', {
110
122
  const {
111
123
  data: { isLogin }
112
124
  } = await applyNative('checkLoginAsync');
125
+
126
+ const isSubUser = context.hostUserInfo?.isSubUser;
113
127
  return {
114
- isLogin
128
+ isLogin,
129
+ isSubUser
115
130
  };
116
131
  }
117
132
  });
@@ -3,6 +3,8 @@ import { createAPI, registerCanIUse, t } from './base';
3
3
  import { createJoliGemPaymentService } from '../payment/payment-service';
4
4
  import { createAPIError } from '@/common/report/errors';
5
5
  import { IPaymentChoice } from '@jolibox/ui/dist/bridge/coin';
6
+ import { ISubscriptionTierData } from '@jolibox/types';
7
+ import { subscriptionService } from '@/native/subscription';
6
8
 
7
9
  const commands = createCommands();
8
10
  const paymentService = createJoliGemPaymentService();
@@ -13,10 +15,10 @@ const purchaseGem = createAPI('purchaseGem', {
13
15
  productId: t.string()
14
16
  })
15
17
  ),
16
- implement: async (params: { productId: string }): Promise<{ totalAmount: string }> => {
18
+ implement: async (params: { productId: string }): Promise<{ totalAmount?: string }> => {
17
19
  const result = await paymentService.purchase(params.productId);
18
20
 
19
- if (result.code !== 'SUCCESS' || !result.data?.totalAmount) {
21
+ if (result.code !== 'SUCCESS') {
20
22
  throw createAPIError({
21
23
  code: -1,
22
24
  msg: '[JoliboxSDK]: purchase gem failed: ' + result.message
@@ -24,7 +26,7 @@ const purchaseGem = createAPI('purchaseGem', {
24
26
  }
25
27
 
26
28
  return {
27
- totalAmount: result.data?.totalAmount
29
+ totalAmount: result.data?.totalAmount ?? ''
28
30
  };
29
31
  }
30
32
  });
@@ -44,9 +46,69 @@ const getGemProducts = createAPI('getGemProducts', {
44
46
  }
45
47
  });
46
48
 
49
+ const getSubscriptionPlans = createAPI('getSubscriptionPlans', {
50
+ paramsSchema: t.tuple(),
51
+ implement: async (): Promise<{ plans: ISubscriptionTierData[] } | undefined> => {
52
+ const subPlans = await subscriptionService.getSubInfo();
53
+
54
+ console.log('[PaymentAPI] getSubscriptionPlans result', subPlans);
55
+ if (!subPlans) {
56
+ throw createAPIError({
57
+ code: -1,
58
+ msg: '[JoliboxSDK]: get subscription plans failed'
59
+ });
60
+ }
61
+ return {
62
+ plans: subPlans
63
+ };
64
+ }
65
+ });
66
+
67
+ const flushSubInfoCache = createAPI('flushSubInfoCache', {
68
+ paramsSchema: t.tuple(),
69
+ implement: async (): Promise<void> => {
70
+ await subscriptionService.updateAllNativePriceCache();
71
+ }
72
+ });
73
+
74
+ const subscribe = createAPI('subscribe', {
75
+ paramsSchema: t.tuple(
76
+ t.object({
77
+ productId: t.string()
78
+ })
79
+ ),
80
+ implement: async (params: {
81
+ productId: string;
82
+ }): Promise<{
83
+ subPlanId: string;
84
+ errMsg: string;
85
+ result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED';
86
+ }> => {
87
+ const tier = await subscriptionService.getTierByProductId(params.productId);
88
+ if (!tier) {
89
+ throw createAPIError({
90
+ code: -1,
91
+ msg: '[JoliboxSDK]: product not found'
92
+ });
93
+ }
94
+ const { basePlanId, appStoreProductId, nativeProductId } = tier;
95
+ const result = await subscriptionService.subscribe({ basePlanId, nativeProductId, appStoreProductId });
96
+ return {
97
+ result: result.result,
98
+ subPlanId: result.subPlanId,
99
+ errMsg: result.message ?? ''
100
+ };
101
+ }
102
+ });
103
+
47
104
  commands.registerCommand('PaymentSDK.purchaseGem', purchaseGem);
48
105
  commands.registerCommand('PaymentSDK.getGemProducts', getGemProducts);
49
106
 
107
+ commands.registerCommand('PaymentSDK.getSubscriptionPlans', getSubscriptionPlans);
108
+ commands.registerCommand('PaymentSDK.subscribe', subscribe);
109
+
110
+ commands.registerCommand('PaymentSDK.flushSubInfoCache', flushSubInfoCache);
111
+
50
112
  registerCanIUse('payment.purchaseGem', {
51
113
  version: '1.2.3'
52
114
  });
@@ -54,3 +116,16 @@ registerCanIUse('payment.purchaseGem', {
54
116
  registerCanIUse('payment.getGemProducts', {
55
117
  version: '1.2.3'
56
118
  });
119
+
120
+ // for test, correct version is 1.3.0
121
+ registerCanIUse('payment.getSubscriptionPlans', {
122
+ version: '1.2.5'
123
+ });
124
+
125
+ registerCanIUse('payment.subscribe', {
126
+ version: '1.2.5'
127
+ });
128
+
129
+ registerCanIUse('payment.flushSubInfoCache', {
130
+ version: '1.2.5'
131
+ });
@@ -9,13 +9,26 @@ import {
9
9
  // Mock dependencies
10
10
  jest.mock('@jolibox/native-bridge', () => ({
11
11
  applyNative: jest.fn(),
12
- onNative: jest.fn()
12
+ onNative: jest.fn(),
13
+ invokeNative: jest.fn()
13
14
  }));
14
15
 
15
16
  jest.mock('@/native/network', () => ({
16
17
  innerFetch: jest.fn()
17
18
  }));
18
19
 
20
+ jest.mock('@/native/api/storage', () => ({
21
+ setGlobalStorage: jest.fn().mockResolvedValue({
22
+ code: 'SUCCESS',
23
+ message: 'success'
24
+ }),
25
+ getGlobalStorage: jest.fn().mockResolvedValue({
26
+ code: 'SUCCESS',
27
+ message: 'success',
28
+ data: null
29
+ })
30
+ }));
31
+
19
32
  // eslint-disable-next-line @typescript-eslint/no-var-requires
20
33
  const { applyNative } = require('@jolibox/native-bridge');
21
34