@jolibox/implement 1.3.1 → 1.3.2
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.
- package/.rush/temp/package-deps_build.json +7 -7
- package/dist/index.js +1 -1
- package/dist/index.native.js +24 -24
- package/dist/native/subscription/subscription-service.d.ts +1 -0
- package/implement.build.log +2 -2
- package/package.json +5 -5
- package/src/native/api/payment.ts +9 -2
- package/src/native/payment/__tests__/payment-service-simple.test.ts +5 -5
- package/src/native/payment/payment-service.ts +1 -1
- package/src/native/payment/registers/jolicoin-iap.ts +0 -4
- package/src/native/rewards/ui/payment-modal.ts +6 -2
- package/src/native/subscription/subscription-service.ts +81 -5
|
@@ -24,6 +24,7 @@ declare class BaseSubscriptionService extends RequestCacheService<SubscriptionRe
|
|
|
24
24
|
message: string;
|
|
25
25
|
result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED';
|
|
26
26
|
}>;
|
|
27
|
+
private pollSubscriptionStatus;
|
|
27
28
|
invokeSubscriptionPanel(): Promise<StandardResponse<{
|
|
28
29
|
result: 'SUCCESS' | 'FAILED';
|
|
29
30
|
subPlanId?: string;
|
package/implement.build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Invoking: npm run clean && npm run build:esm && tsc
|
|
2
2
|
|
|
3
|
-
> @jolibox/implement@1.3.
|
|
3
|
+
> @jolibox/implement@1.3.2 clean
|
|
4
4
|
> rimraf ./dist
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @jolibox/implement@1.3.
|
|
7
|
+
> @jolibox/implement@1.3.2 build:esm
|
|
8
8
|
> BUILD_VERSION=$(node -p "require('./package.json').version") node esbuild.config.js --format=esm
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jolibox/implement",
|
|
3
3
|
"description": "This project is Jolibox JS-SDk implement for Native && H5",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.2",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jolibox/common": "1.3.
|
|
10
|
-
"@jolibox/types": "1.3.
|
|
11
|
-
"@jolibox/native-bridge": "1.3.
|
|
12
|
-
"@jolibox/ads": "1.3.
|
|
9
|
+
"@jolibox/common": "1.3.2",
|
|
10
|
+
"@jolibox/types": "1.3.2",
|
|
11
|
+
"@jolibox/native-bridge": "1.3.2",
|
|
12
|
+
"@jolibox/ads": "1.3.2",
|
|
13
13
|
"localforage": "1.10.0",
|
|
14
14
|
"@jolibox/ui": "1.0.0",
|
|
15
15
|
"web-vitals": "4.2.4"
|
|
@@ -34,7 +34,7 @@ const purchaseGem = createAPI('purchaseGem', {
|
|
|
34
34
|
const getGemProducts = createAPI('getGemProducts', {
|
|
35
35
|
paramsSchema: t.tuple(),
|
|
36
36
|
implement: async (): Promise<{ products: IPaymentChoice[] }> => {
|
|
37
|
-
const choices = (await paymentService.
|
|
37
|
+
const choices = (await paymentService.getProductsInfoWithBalance())?.paymentChoices ?? [];
|
|
38
38
|
console.info('choices', choices);
|
|
39
39
|
return {
|
|
40
40
|
products: choices.map((choice) => ({
|
|
@@ -92,7 +92,14 @@ const subscribe = createAPI('subscribe', {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
const { basePlanId, appStoreProductId, nativeProductId } = tier;
|
|
95
|
-
|
|
95
|
+
|
|
96
|
+
// Use subscription service with built-in compensation polling
|
|
97
|
+
const result = await subscriptionService.subscribe({
|
|
98
|
+
basePlanId,
|
|
99
|
+
nativeProductId,
|
|
100
|
+
appStoreProductId
|
|
101
|
+
});
|
|
102
|
+
|
|
96
103
|
return {
|
|
97
104
|
result: result.result,
|
|
98
105
|
subPlanId: result.subPlanId,
|
|
@@ -151,9 +151,9 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
151
151
|
const promise = paymentService.getProductsInfo();
|
|
152
152
|
|
|
153
153
|
// Fast forward past timeout (1 second)
|
|
154
|
-
jest.advanceTimersByTime(
|
|
154
|
+
jest.advanceTimersByTime(3000);
|
|
155
155
|
|
|
156
|
-
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after
|
|
156
|
+
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 3 seconds');
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
it('should handle cache expiration', async () => {
|
|
@@ -217,16 +217,16 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
|
-
},
|
|
220
|
+
}, 5000);
|
|
221
221
|
})
|
|
222
222
|
);
|
|
223
223
|
|
|
224
224
|
const promise = paymentService.getProductsInfo();
|
|
225
225
|
|
|
226
226
|
// Advance timers to 1 second to trigger timeout
|
|
227
|
-
jest.advanceTimersByTime(
|
|
227
|
+
jest.advanceTimersByTime(3000);
|
|
228
228
|
|
|
229
|
-
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after
|
|
229
|
+
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 3 seconds');
|
|
230
230
|
});
|
|
231
231
|
|
|
232
232
|
it('should increment failure count on failed requests', async () => {
|
|
@@ -167,7 +167,7 @@ class BasePaymentService extends RequestCacheService<
|
|
|
167
167
|
constructor(private apiEndpoint: string, private paymentType: PaymentPurchaseType) {
|
|
168
168
|
super(new PaymentRequestAdapter(), {
|
|
169
169
|
duration: 10 * 60 * 1000, // 10分钟缓存 paymentChoices
|
|
170
|
-
timeout:
|
|
170
|
+
timeout: 3000 // 3秒超时
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -40,10 +40,6 @@ onNative('onPaymentStateChange', (data) => {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (status === 'SUCCESS') {
|
|
43
|
-
createToast(`{slot-success} {slot-i18n-jolicoin.unlockSuccess}`, {
|
|
44
|
-
position: 'center',
|
|
45
|
-
duration: 3000
|
|
46
|
-
});
|
|
47
43
|
deferred.resolve({
|
|
48
44
|
code: 'SUCCESS' as ResponseType,
|
|
49
45
|
message: 'jolicoin payment success',
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
UseModalFrequencyEventName,
|
|
8
8
|
IUseModalFrequencyConfig
|
|
9
9
|
} from '@/common/rewards/reward-emitter';
|
|
10
|
-
import { createPaymentJolicoinModal } from '@jolibox/ui';
|
|
10
|
+
import { createPaymentJolicoinModal, createToast } from '@jolibox/ui';
|
|
11
11
|
import { innerFetch as fetch } from '@/native/network';
|
|
12
12
|
import { StandardResponse } from '@jolibox/types';
|
|
13
13
|
import { context } from '@/common/context';
|
|
@@ -220,6 +220,7 @@ rewardsEmitter.on(
|
|
|
220
220
|
productId,
|
|
221
221
|
appStoreProductId
|
|
222
222
|
});
|
|
223
|
+
|
|
223
224
|
loading.hide();
|
|
224
225
|
track(paymentConfig.trackEvents.payResult, {
|
|
225
226
|
eventType: EventType.Other,
|
|
@@ -232,12 +233,15 @@ rewardsEmitter.on(
|
|
|
232
233
|
?.totalAmountStr ?? '',
|
|
233
234
|
payResult: code
|
|
234
235
|
});
|
|
235
|
-
console.log('payment result', code);
|
|
236
236
|
if (code !== 'SUCCESS') {
|
|
237
237
|
/** add timeout for google panel closed */
|
|
238
238
|
console.info('[JoliboxSDK] payment failed in payment.invokePaymet');
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
|
+
createToast(`{slot-success} {slot-i18n-jolicoin.unlockSuccess}`, {
|
|
242
|
+
position: 'center',
|
|
243
|
+
duration: 3000
|
|
244
|
+
});
|
|
241
245
|
rewardsEmitter.emit(PaymentResultEventName, {
|
|
242
246
|
paymentResult: 'SUCCESS',
|
|
243
247
|
currency: currencyType
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ISubscriptionPlanData } from './type';
|
|
2
2
|
import { ISubscriptionTierData } from '@jolibox/types';
|
|
3
|
-
import { Deferred, isUndefinedOrNull } from '@jolibox/common';
|
|
3
|
+
import { Deferred, isUndefinedOrNull, sleep } from '@jolibox/common';
|
|
4
4
|
import { StandardResponse } from '@jolibox/types';
|
|
5
5
|
import { applyNative, onNative } from '@jolibox/native-bridge';
|
|
6
6
|
import { innerFetch as fetch } from '@/native/network';
|
|
@@ -12,6 +12,16 @@ import { createSubscriptionInternalError } from './registers/base';
|
|
|
12
12
|
import { SubscriptionErrorCodeMap } from './registers/type';
|
|
13
13
|
import { ResponseType } from '@jolibox/types';
|
|
14
14
|
|
|
15
|
+
interface IUserSubData {
|
|
16
|
+
subPlanId: string;
|
|
17
|
+
tier: string;
|
|
18
|
+
domain: string;
|
|
19
|
+
status: 'ACTIVE' | 'CANCELED' | 'IN_GRACE' | 'EXPIRED';
|
|
20
|
+
validUntil: number;
|
|
21
|
+
planType: string;
|
|
22
|
+
haveNextPayment: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
// Request/Response interfaces for RequestCacheService
|
|
16
26
|
type SubscriptionRequest = Record<string, never>;
|
|
17
27
|
|
|
@@ -215,19 +225,85 @@ class BaseSubscriptionService extends RequestCacheService<
|
|
|
215
225
|
appStoreProductId: string;
|
|
216
226
|
}): Promise<{ subPlanId: string; message: string; result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED' }> {
|
|
217
227
|
console.log('[SubscriptionService] subscribe params', subscriptionHelper);
|
|
218
|
-
|
|
228
|
+
|
|
229
|
+
// Start subscription and compensation polling in parallel
|
|
230
|
+
const abortController = new AbortController();
|
|
231
|
+
const subscribePromise = subscriptionHelper.invokeSubscription('SUB_APP', {
|
|
219
232
|
productId: params.nativeProductId,
|
|
220
233
|
appStoreProductId: params.appStoreProductId,
|
|
221
234
|
planId: params.basePlanId
|
|
222
235
|
});
|
|
236
|
+
const compensationPromise = this.pollSubscriptionStatus(abortController.signal);
|
|
237
|
+
|
|
238
|
+
// Wait for both to complete or first success
|
|
239
|
+
const result = (await Promise.race([
|
|
240
|
+
subscribePromise.then((subscribeResult) => {
|
|
241
|
+
// Cancel polling when subscribe completes
|
|
242
|
+
abortController.abort();
|
|
243
|
+
return {
|
|
244
|
+
result: subscribeResult.code as 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED',
|
|
245
|
+
subPlanId: subscribeResult.data?.subPlanId,
|
|
246
|
+
message: subscribeResult.message
|
|
247
|
+
};
|
|
248
|
+
}),
|
|
249
|
+
compensationPromise.then((pollingResult) => {
|
|
250
|
+
if (pollingResult?.result === 'SUCCESS') {
|
|
251
|
+
abortController.abort();
|
|
252
|
+
return pollingResult;
|
|
253
|
+
}
|
|
254
|
+
// If polling fails or times out, wait forever (let subscribe resolve)
|
|
255
|
+
return new Promise(() => {
|
|
256
|
+
console.log('[SubscriptionPolling] Timeout for subscriptionService.subscribe completion');
|
|
257
|
+
});
|
|
258
|
+
})
|
|
259
|
+
])) as { result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED'; subPlanId: string; message?: string };
|
|
223
260
|
|
|
224
261
|
return {
|
|
225
|
-
result: result.
|
|
226
|
-
subPlanId: result.
|
|
227
|
-
message: result.message
|
|
262
|
+
result: result.result,
|
|
263
|
+
subPlanId: result.subPlanId,
|
|
264
|
+
message: result.message ?? ''
|
|
228
265
|
};
|
|
229
266
|
}
|
|
230
267
|
|
|
268
|
+
// Polling function for subscription status compensation
|
|
269
|
+
private async pollSubscriptionStatus(abortSignal: AbortSignal): Promise<{
|
|
270
|
+
result: 'SUCCESS' | 'FAILED' | 'FAILURE_SUBSCRIPTED';
|
|
271
|
+
subPlanId: string;
|
|
272
|
+
message?: string;
|
|
273
|
+
} | void> {
|
|
274
|
+
const maxAttempts = 10; // Poll for 50 seconds max
|
|
275
|
+
const interval = 5000; // 5 second intervals
|
|
276
|
+
|
|
277
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
278
|
+
if (abortSignal.aborted) {
|
|
279
|
+
console.log('[SubscriptionPolling] Aborted due to subscribe completion');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const { response } = await fetch<StandardResponse<IUserSubData>>('/api/subs/info', {
|
|
285
|
+
method: 'GET',
|
|
286
|
+
appendHostCookie: true,
|
|
287
|
+
responseType: 'json'
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (response.data?.data?.status === 'ACTIVE') {
|
|
291
|
+
return {
|
|
292
|
+
result: 'SUCCESS',
|
|
293
|
+
subPlanId: response.data.data.subPlanId,
|
|
294
|
+
message: 'Subscription activated via polling'
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn(`[SubscriptionPolling] Attempt ${attempt + 1} failed:`, error);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (attempt < maxAttempts - 1 && !abortSignal.aborted) {
|
|
302
|
+
await sleep(interval);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
231
307
|
async invokeSubscriptionPanel(): Promise<
|
|
232
308
|
StandardResponse<{ result: 'SUCCESS' | 'FAILED'; subPlanId?: string }>
|
|
233
309
|
> {
|