@jolibox/implement 1.2.8 → 1.3.0

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 (65) hide show
  1. package/.rush/temp/package-deps_build.json +37 -27
  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/registers/use-gem-only.ts +5 -2
  32. package/src/common/rewards/registers/use-gem.ts +5 -2
  33. package/src/common/rewards/registers/use-jolicoin-only.ts +5 -2
  34. package/src/common/rewards/registers/use-jolicoin.ts +5 -2
  35. package/src/common/rewards/registers/use-subscription.ts +5 -2
  36. package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +33 -11
  37. package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +34 -13
  38. package/src/common/rewards/registers/utils/common.ts +9 -0
  39. package/src/common/rewards/registers/utils/subscription/commands/use-subscription.ts +16 -0
  40. package/src/common/rewards/registers/utils/subscription/sub-handler.ts +21 -6
  41. package/src/common/rewards/reward-emitter.ts +8 -0
  42. package/src/common/rewards/type.ts +1 -1
  43. package/src/common/utils/index.ts +1 -1
  44. package/src/h5/api/ads.ts +6 -3
  45. package/src/h5/bootstrap/auth/__tests__/auth.test.ts +15 -9
  46. package/src/h5/bootstrap/auth/sub.ts +1 -1
  47. package/src/native/api/ads.ts +43 -6
  48. package/src/native/api/call-host-method.ts +5 -61
  49. package/src/native/api/login.ts +22 -7
  50. package/src/native/api/payment.ts +79 -5
  51. package/src/native/payment/__tests__/payment-service-simple.test.ts +14 -1
  52. package/src/native/payment/payment-service.ts +26 -27
  53. package/src/native/payment/utils/__tests__/cache-with-storage.test.ts +414 -0
  54. package/src/native/payment/utils/cache-with-storage.ts +112 -0
  55. package/src/native/rewards/check-frequency.ts +6 -0
  56. package/src/native/rewards/index.ts +1 -0
  57. package/src/native/rewards/ui/payment-modal.ts +2 -2
  58. package/src/native/rewards/ui/subscription-modal.ts +81 -0
  59. package/src/native/subscription/index.ts +12 -0
  60. package/src/native/subscription/registers/base.ts +88 -0
  61. package/src/native/subscription/registers/sub-app.ts +258 -0
  62. package/src/native/subscription/registers/type.ts +13 -0
  63. package/src/native/subscription/subscription-helper.ts +53 -0
  64. package/src/native/subscription/subscription-service.ts +339 -0
  65. package/src/native/subscription/type.ts +18 -0
@@ -0,0 +1,88 @@
1
+ import { StandardResponse } from '@jolibox/types';
2
+ import type { IPlaceSubscriptionOrderResponse } from './type';
3
+ import { SubscriptionErrorCodeMap } from './type';
4
+ import { UserPaymentError, InternalPaymentError } from '@jolibox/common';
5
+ import { context } from '@/common/context';
6
+ import { innerFetch as fetch } from '@/native/network';
7
+
8
+ type SubscriptionStatus = 'INIT' | 'PENDING' | 'SUCCESS' | 'FAILED';
9
+
10
+ const createSubscriptionErrorFactory = () => {
11
+ const [createSubscriptionError, createSubscriptionInternalError] = [
12
+ UserPaymentError,
13
+ InternalPaymentError
14
+ ].map((ctor) => {
15
+ return (errMsg: string, errNo: number, extra?: Record<string, unknown>) => {
16
+ return new ctor(errMsg, errNo, extra);
17
+ };
18
+ });
19
+ return {
20
+ createSubscriptionError,
21
+ createSubscriptionInternalError
22
+ };
23
+ };
24
+
25
+ const { createSubscriptionError, createSubscriptionInternalError } = createSubscriptionErrorFactory();
26
+ export { createSubscriptionError, createSubscriptionInternalError };
27
+
28
+ const CALLBACK_HOST = context.testMode ? 'https://stg-game.jolibox.com' : 'https://game.jolibox.com';
29
+
30
+ export abstract class BaseSubscriptionRegister<
31
+ T extends Record<string, any>,
32
+ E extends Record<string, any>,
33
+ R = void
34
+ > {
35
+ private _orderId: string | null = null;
36
+ private _orderStatus: SubscriptionStatus = 'INIT';
37
+
38
+ startSubscription = async (params: T): Promise<StandardResponse<R>> => {
39
+ if (this._orderId) {
40
+ await this.cancelOrder(this.orderId as string);
41
+ }
42
+
43
+ const { code, message } = await this.placeOrder(params);
44
+ if (code !== 'SUCCESS') {
45
+ throw createSubscriptionError(message, SubscriptionErrorCodeMap.PlaceOrderFailed);
46
+ }
47
+
48
+ // invoke subscription flow
49
+ return await this.subscribe(params);
50
+ };
51
+
52
+ protected generateCallbackUrl = () => {
53
+ // Set subscription callback parameters
54
+ const originalSearch = new URLSearchParams('');
55
+ originalSearch.set('utm_source', context.deviceInfo.platform);
56
+ originalSearch.set(
57
+ 'joliSource',
58
+ context.encodeJoliSourceQuery({
59
+ ...this.generateSubscriptionContext()
60
+ })
61
+ );
62
+ return `${CALLBACK_HOST}/subscription/callback?${originalSearch.toString()}`;
63
+ };
64
+
65
+ protected setOrderId(orderId: string) {
66
+ this._orderId = orderId;
67
+ }
68
+
69
+ protected setOrderStatus(status: SubscriptionStatus) {
70
+ this._orderStatus = status;
71
+ }
72
+
73
+ protected createSubscriptionError = createSubscriptionError;
74
+ protected createSubscriptionInternalError = createSubscriptionInternalError;
75
+
76
+ abstract placeOrder(params: T): Promise<StandardResponse<IPlaceSubscriptionOrderResponse<E>>>;
77
+ abstract cancelOrder(orderId: string): Promise<StandardResponse<void>>;
78
+ abstract generateSubscriptionContext(): Record<string, any>;
79
+ abstract subscribe(params: T): Promise<StandardResponse<R>>;
80
+
81
+ get orderId(): string | null {
82
+ return this._orderId;
83
+ }
84
+
85
+ get status(): SubscriptionStatus {
86
+ return this._orderStatus;
87
+ }
88
+ }
@@ -0,0 +1,258 @@
1
+ import { StandardResponse } from '@jolibox/types';
2
+ import { context } from '@/common/context';
3
+ import { innerFetch as fetch } from '@/native/network';
4
+ import { BaseSubscriptionRegister } from './base';
5
+ import type { IPlaceSubscriptionOrderResponse } from './type';
6
+ import { PaymentErrorCodeMap } from '@/native/payment/registers/type';
7
+ import { createPaymentInternalError } from '@/native/payment/registers/base';
8
+ import { ResponseType } from '@jolibox/types';
9
+ import { Deferred } from '@jolibox/common';
10
+ import { invokeNative, onNative } from '@jolibox/native-bridge';
11
+ import { isNumber } from '@jolibox/common';
12
+ import { SubscriptionErrorCodeMap } from './type';
13
+
14
+ export interface IPlaceOrderSubAppParams {
15
+ basePlanId: string;
16
+ appStoreProductId: string;
17
+ productId: string;
18
+ }
19
+
20
+ export interface ISubAppProductInfo {
21
+ subPlanId: string;
22
+ }
23
+
24
+ export interface ISubAppSubscriptionContext {
25
+ gameId: string;
26
+ productId: string;
27
+ planId: string;
28
+ }
29
+
30
+ /** global subscription */
31
+
32
+ onNative('onGlobalSubscriptionStateChange', (data) => {
33
+ const { status, orderResponse } = data;
34
+ console.log('[onGlobalSubscriptionStateChange] data', data);
35
+ if (status === 'SUCCESS' && (orderResponse?.subPlanId || orderResponse?.tickedId)) {
36
+ // user must be logined
37
+ context.onEnvConfigChanged({
38
+ hostUserInfo: {
39
+ isLogin: true,
40
+ isSubUser: true
41
+ }
42
+ });
43
+ }
44
+ });
45
+
46
+ const pendingSubscriptions = new Map<string, Deferred<StandardResponse<{ subPlanId: string }>>>();
47
+
48
+ onNative('onSubscritptionStateChange', (data) => {
49
+ const { subUUID, status, orderResponse } = data;
50
+
51
+ console.log('[onSubscritptionStateChange] data', data);
52
+ const deferred = pendingSubscriptions.get(subUUID);
53
+ if (!deferred) {
54
+ return;
55
+ }
56
+
57
+ if (status === 'SUCCESS') {
58
+ deferred.resolve({
59
+ code: 'SUCCESS' as ResponseType,
60
+ message: 'jolicoin payment success',
61
+ data: { subPlanId: orderResponse?.subPlanId ?? orderResponse.tickedId }
62
+ });
63
+ } else {
64
+ if (status === 'FAILED') {
65
+ if (isNumber(orderResponse?.errNo) && orderResponse?.errNo !== 0) {
66
+ throw createPaymentInternalError(
67
+ `subscription failed: ${orderResponse?.errMsg}`,
68
+ SubscriptionErrorCodeMap.SubscriptionFailed
69
+ );
70
+ }
71
+ }
72
+ deferred.resolve({
73
+ code: status as ResponseType,
74
+ message: orderResponse?.errMsg ?? 'UNKNOWN_ERROR'
75
+ });
76
+ }
77
+
78
+ // Clean up map
79
+ pendingSubscriptions.delete(subUUID);
80
+ });
81
+
82
+ const internalSubscribe = async (params: {
83
+ appStoreProductId: string;
84
+ basePlanId: string;
85
+ productId: string;
86
+ appAccountToken?: string;
87
+ }) => {
88
+ const deferred = new Deferred<StandardResponse<{ subPlanId: string }>>();
89
+ let targetOrderUUID: string | undefined;
90
+
91
+ try {
92
+ console.log('[internalSubscribe] requestSubscriptionSync params', params);
93
+ const response = invokeNative('requestSubscriptionSync', {
94
+ params: {
95
+ basePlanId: params.basePlanId,
96
+ appStoreProductId: params.appStoreProductId,
97
+ productId: params.productId
98
+ },
99
+ ...(params.appAccountToken
100
+ ? {
101
+ paymentBody: {
102
+ appAccountToken: params.appAccountToken,
103
+ appStoreProductId: params.appStoreProductId
104
+ }
105
+ }
106
+ : {})
107
+ });
108
+ targetOrderUUID = response.data?.subUUID;
109
+
110
+ if (!targetOrderUUID) {
111
+ throw createPaymentInternalError(
112
+ 'subUUID is null',
113
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
114
+ );
115
+ }
116
+
117
+ pendingSubscriptions.set(targetOrderUUID, deferred);
118
+ } catch (e) {
119
+ throw createPaymentInternalError(
120
+ JSON.stringify(e) ?? 'jolicoin payment failed',
121
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
122
+ );
123
+ }
124
+
125
+ return deferred.promise;
126
+ };
127
+
128
+ // Android 实现 - 不需要 placeOrder
129
+ class SubAppAndroidSubscriptionRegister extends BaseSubscriptionRegister<
130
+ IPlaceOrderSubAppParams,
131
+ ISubAppProductInfo,
132
+ { subPlanId: string }
133
+ > {
134
+ constructor(private readonly productId: string, private readonly planId: string) {
135
+ super();
136
+ }
137
+
138
+ async placeOrder(
139
+ _params: IPlaceOrderSubAppParams
140
+ ): Promise<StandardResponse<IPlaceSubscriptionOrderResponse<ISubAppProductInfo>>> {
141
+ // Android 不需要实际下单,直接返回成功
142
+ return {
143
+ code: 'SUCCESS',
144
+ message: 'Android subscription order placed locally'
145
+ };
146
+ }
147
+
148
+ cancelOrder(_orderId: string): Promise<StandardResponse<void>> {
149
+ // Android 本地取消
150
+ return Promise.resolve({
151
+ code: 'SUCCESS',
152
+ message: 'Android order cancelled locally',
153
+ data: undefined
154
+ });
155
+ }
156
+
157
+ generateSubscriptionContext(): ISubAppSubscriptionContext {
158
+ return {
159
+ gameId: context.mpId,
160
+ productId: this.productId,
161
+ planId: this.planId
162
+ };
163
+ }
164
+
165
+ async subscribe(params: IPlaceOrderSubAppParams): Promise<StandardResponse<{ subPlanId: string }>> {
166
+ const response = await internalSubscribe(params);
167
+ return response;
168
+ }
169
+ }
170
+
171
+ // iOS 实现 - 需要 placeOrder
172
+ class SubAppIOSSubscriptionRegister extends BaseSubscriptionRegister<
173
+ IPlaceOrderSubAppParams,
174
+ ISubAppProductInfo,
175
+ { subPlanId: string }
176
+ > {
177
+ private subscriptionToken: string | undefined;
178
+
179
+ constructor(private readonly productId: string, private readonly planId: string) {
180
+ super();
181
+ }
182
+
183
+ async placeOrder(
184
+ params: IPlaceOrderSubAppParams
185
+ ): Promise<StandardResponse<IPlaceSubscriptionOrderResponse<ISubAppProductInfo>>> {
186
+ console.log('----start place order for iOS subscription----');
187
+
188
+ const env = invokeNative('envSync');
189
+ const accountToken = env?.data?.hostUserInfo?.appAccountToken;
190
+ if (!accountToken) {
191
+ throw createPaymentInternalError(
192
+ 'appAccountToken is null',
193
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
194
+ );
195
+ }
196
+ this.subscriptionToken = accountToken;
197
+ return {
198
+ code: 'SUCCESS' as ResponseType,
199
+ message: 'placeorder sub-app success in native'
200
+ };
201
+ }
202
+
203
+ async cancelOrder(orderId: string): Promise<StandardResponse<void>> {
204
+ // Android 本地取消
205
+ return Promise.resolve({
206
+ code: 'SUCCESS',
207
+ message: 'Android order cancelled locally',
208
+ data: undefined
209
+ });
210
+ }
211
+
212
+ generateSubscriptionContext(): ISubAppSubscriptionContext {
213
+ return {
214
+ gameId: context.mpId,
215
+ productId: this.productId,
216
+ planId: this.planId
217
+ };
218
+ }
219
+
220
+ async subscribe(params: IPlaceOrderSubAppParams): Promise<StandardResponse<{ subPlanId: string }>> {
221
+ return await internalSubscribe({ ...params, appAccountToken: this.subscriptionToken });
222
+ }
223
+ }
224
+
225
+ const SubAppSubscriptionRegisterMap = {
226
+ android: SubAppAndroidSubscriptionRegister,
227
+ ios: SubAppIOSSubscriptionRegister
228
+ } as const;
229
+
230
+ type SubAppSubscriptionRegister = SubAppAndroidSubscriptionRegister | SubAppIOSSubscriptionRegister;
231
+
232
+ export function createSubAppSubscriptionHandler() {
233
+ const productSubscriptionMap = new Map<string, SubAppSubscriptionRegister>();
234
+
235
+ return async (params: { productId: string; appStoreProductId: string; planId: string }) => {
236
+ console.log('[SubAppSubscriptionHandler] createSubAppSubscriptionHandler params', params);
237
+ const { productId, planId } = params;
238
+ const cacheKey = `${productId}_${planId}`;
239
+
240
+ let instance = productSubscriptionMap.get(cacheKey);
241
+ if (!instance) {
242
+ console.log('[SubAppSubscriptionHandler] createSubAppSubscriptionHandler instance not found', cacheKey);
243
+ const Register =
244
+ SubAppSubscriptionRegisterMap[context.platform as keyof typeof SubAppSubscriptionRegisterMap];
245
+ if (!Register) {
246
+ throw new Error(`Platform ${context.platform} not supported for sub-app subscription`);
247
+ }
248
+ instance = new Register(productId, planId);
249
+ productSubscriptionMap.set(cacheKey, instance);
250
+ }
251
+
252
+ return instance.startSubscription({
253
+ basePlanId: planId,
254
+ appStoreProductId: params.appStoreProductId,
255
+ productId
256
+ });
257
+ };
258
+ }
@@ -0,0 +1,13 @@
1
+ import { StandardResponse } from '@jolibox/types';
2
+
3
+ export interface IPlaceSubscriptionOrderResponse<T = any> {
4
+ orderId: string;
5
+ productInfo: T;
6
+ }
7
+
8
+ export const SubscriptionErrorCodeMap = {
9
+ PlaceOrderFailed: 1001,
10
+ CancelOrderFailed: 1002,
11
+ SubscriptionFailed: 1003,
12
+ InvalidPlan: 1004
13
+ } as const;
@@ -0,0 +1,53 @@
1
+ export type SubscriptionType = 'SUB_APP'; // 目前支持sub-app平台
2
+
3
+ import { StandardResponse } from '@jolibox/types';
4
+ import { reportError } from '@/common/report/errors/report';
5
+ import { BaseError } from '@jolibox/common';
6
+
7
+ export type SubscriptionResult<T> = StandardResponse<T>;
8
+
9
+ export interface SubscriptionHandlerMap {
10
+ SUB_APP: (params: {
11
+ productId: string;
12
+ appStoreProductId: string;
13
+ planId: string;
14
+ }) => Promise<SubscriptionResult<{ subPlanId: string }>>; // sub-app subscription
15
+ }
16
+
17
+ export type SubscriptionHandler<T extends SubscriptionType> = SubscriptionHandlerMap[T];
18
+
19
+ export function createSubscriptionHelper() {
20
+ const subscriptionHandlers = new Map<SubscriptionType, SubscriptionHandler<any>>();
21
+
22
+ return {
23
+ registerSubscriptionHandler<T extends SubscriptionType>(type: T, handler: SubscriptionHandler<T>) {
24
+ subscriptionHandlers.set(type, handler);
25
+ },
26
+ async invokeSubscription<T extends SubscriptionType>(
27
+ type: T,
28
+ ...args: Parameters<SubscriptionHandler<T>>
29
+ ) {
30
+ const subscriptionHandler = subscriptionHandlers.get(type);
31
+ if (!subscriptionHandler) {
32
+ return {
33
+ code: 'FAILED',
34
+ message: `[joliboxSDK]: ${type} subscription not supported in this platform`
35
+ };
36
+ }
37
+ try {
38
+ const result = await subscriptionHandler(...args);
39
+ return result;
40
+ } catch (e) {
41
+ reportError(e as BaseError);
42
+ return {
43
+ code: 'FAILED',
44
+ message: `[joliboxSDK]: subscription error ${e}`
45
+ };
46
+ }
47
+ }
48
+ };
49
+ }
50
+
51
+ export type SubscriptionHelper = ReturnType<typeof createSubscriptionHelper>;
52
+
53
+ export const subscriptionHelper = createSubscriptionHelper();