@jolibox/implement 1.1.29-beta.1 → 1.1.30

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 (32) hide show
  1. package/.rush/temp/package-deps_build.json +21 -19
  2. package/dist/common/context/index.d.ts +6 -0
  3. package/dist/common/rewards/reward-emitter.d.ts +2 -1
  4. package/dist/index.js +9 -9
  5. package/dist/index.native.js +154 -111
  6. package/dist/native/api/base.d.ts +1 -0
  7. package/dist/native/payment/payment-helper.d.ts +5 -1
  8. package/dist/native/payment/registers/base.d.ts +8 -6
  9. package/dist/native/payment/registers/const.d.ts +3 -0
  10. package/dist/native/payment/registers/jolicoin-iap.d.ts +21 -0
  11. package/implement.build.log +2 -2
  12. package/package.json +5 -5
  13. package/src/common/context/index.ts +21 -2
  14. package/src/common/rewards/registers/use-ads.ts +1 -0
  15. package/src/common/rewards/registers/utils/coins/index.ts +0 -1
  16. package/src/common/rewards/reward-emitter.ts +2 -1
  17. package/src/native/api/base.ts +4 -0
  18. package/src/native/api/lifecycle.ts +3 -0
  19. package/src/native/api/login.ts +5 -5
  20. package/src/native/api/navigate.ts +4 -1
  21. package/src/native/api/request.ts +3 -3
  22. package/src/native/bootstrap/init-env.ts +10 -0
  23. package/src/native/network/create-fetch.ts +0 -18
  24. package/src/native/payment/index.ts +2 -0
  25. package/src/native/payment/payment-helper.ts +2 -1
  26. package/src/native/payment/registers/base.ts +15 -19
  27. package/src/native/payment/registers/const.ts +14 -0
  28. package/src/native/payment/registers/joli-coin.ts +10 -0
  29. package/src/native/payment/registers/jolicoin-iap.ts +220 -0
  30. package/src/native/rewards/check-frequency.ts +2 -1
  31. package/src/native/rewards/index.ts +92 -14
  32. package/src/native/ui/retention.ts +2 -2
@@ -0,0 +1,220 @@
1
+ import { innerFetch as fetch } from '@/native/network';
2
+ import { BasePaymentRegister, createPaymentError, createPaymentInternalError } from './base';
3
+ import { PaymentErrorCodeMap, IPlaceOrderResponse } from './type';
4
+ import { ResponseType, StandardResponse } from '@jolibox/types';
5
+ import { applyNative, onNative } from '@jolibox/native-bridge';
6
+ import { Deferred, isNumber, platform } from '@jolibox/common';
7
+ import { context } from '@/common/context';
8
+ import { createToast } from '@jolibox/ui';
9
+ import { IPaymentIAPFailedStatusMap } from './const';
10
+
11
+ export interface IPlaceOrderJoliCoinIAPParamas {
12
+ productId: string;
13
+ appStoreProductId: string;
14
+ }
15
+
16
+ export interface IJoliCoinProductInfo {
17
+ quantity: number;
18
+ }
19
+
20
+ interface IJolicoinPaymentIAPResponse {
21
+ totalAmount: string;
22
+ }
23
+
24
+ export interface IJolicoinPaymentIAPContext {
25
+ type: 'JOLI_COIN_IAP_CONTEXT';
26
+ state: 'TO_CHECKOUT' | 'TO_VALIDATE';
27
+ productId: string;
28
+ appStoreProductId: string;
29
+ }
30
+
31
+ const pendingPayments = new Map<string, Deferred<StandardResponse<{ totalAmount: string }>>>();
32
+
33
+ onNative('onPaymentStateChange', (data) => {
34
+ console.info('onPaymentStateChange', data);
35
+ const { orderUUID, status, totalAmount, orderResponse } = data;
36
+
37
+ const deferred = pendingPayments.get(orderUUID);
38
+ if (!deferred) {
39
+ return;
40
+ }
41
+
42
+ if (status === 'SUCCESS') {
43
+ createToast(`{slot-success} {slot-i18n-jolicoin.unlockSuccess}`, {
44
+ position: 'center',
45
+ duration: 3000
46
+ });
47
+ deferred.resolve({
48
+ code: 'SUCCESS' as ResponseType,
49
+ message: 'jolicoin payment success',
50
+ data: { totalAmount }
51
+ });
52
+ } else {
53
+ if (status === 'FAILED') {
54
+ if (isNumber(orderResponse?.errNo) && orderResponse?.errNo !== 0) {
55
+ throw createPaymentInternalError(
56
+ `jolicoin payment failed: ${orderResponse?.errMsg}`,
57
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
58
+ );
59
+ }
60
+ }
61
+ const failedStatus = IPaymentIAPFailedStatusMap[status] ?? 'unlockFailed';
62
+ createToast(`{slot-error} {slot-i18n-jolicoin.${failedStatus}}`, {
63
+ position: 'center',
64
+ duration: 3000
65
+ });
66
+ deferred.resolve({
67
+ code: 'FAILED' as ResponseType,
68
+ message: 'jolicoin payment failed'
69
+ });
70
+ }
71
+
72
+ // Clean up map
73
+ pendingPayments.delete(orderUUID);
74
+ });
75
+
76
+ const payInApp = async (params: { appStoreProductId: string; appAccountToken?: string }) => {
77
+ const deferred = new Deferred<StandardResponse<{ totalAmount: string }>>();
78
+ let targetOrderUUID: string | undefined;
79
+
80
+ try {
81
+ const response = await applyNative('requestPaymentSync', {
82
+ paymentBody: {
83
+ appStoreProductId: params.appStoreProductId,
84
+ appAccountToken: params.appAccountToken
85
+ }
86
+ });
87
+ targetOrderUUID = response.data?.orderUUID;
88
+
89
+ console.info('---payInApp---', response);
90
+ if (!targetOrderUUID) {
91
+ throw createPaymentInternalError(
92
+ 'orderUUID is null',
93
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
94
+ );
95
+ }
96
+
97
+ pendingPayments.set(targetOrderUUID, deferred);
98
+ } catch (e) {
99
+ throw createPaymentInternalError(
100
+ JSON.stringify(e) ?? 'jolicoin payment failed',
101
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
102
+ );
103
+ }
104
+
105
+ return deferred.promise;
106
+ };
107
+
108
+ class JolicoinIAPAndroidPaymentResiter extends BasePaymentRegister<
109
+ IPlaceOrderJoliCoinIAPParamas,
110
+ IJoliCoinProductInfo,
111
+ IJolicoinPaymentIAPResponse
112
+ > {
113
+ constructor(readonly productId: string, readonly appStoreProductId: string) {
114
+ super();
115
+ }
116
+ async placeOrder(
117
+ params: IPlaceOrderJoliCoinIAPParamas
118
+ ): Promise<StandardResponse<IPlaceOrderResponse<IJoliCoinProductInfo>, unknown>> {
119
+ return {
120
+ code: 'SUCCESS' as ResponseType,
121
+ message: 'placeorder jolicoin success in native'
122
+ };
123
+ }
124
+
125
+ generatePaymentContext = (): IJolicoinPaymentIAPContext => {
126
+ return {
127
+ type: 'JOLI_COIN_IAP_CONTEXT',
128
+ state: 'TO_VALIDATE',
129
+ productId: this.productId,
130
+ appStoreProductId: this.appStoreProductId
131
+ };
132
+ };
133
+
134
+ pay = async (): Promise<StandardResponse<{ totalAmount: string }>> => {
135
+ return await payInApp({ appStoreProductId: this.appStoreProductId });
136
+ };
137
+ }
138
+
139
+ class JolicoinIAPIOSPaymentResiter extends BasePaymentRegister<
140
+ IPlaceOrderJoliCoinIAPParamas,
141
+ IJoliCoinProductInfo,
142
+ IJolicoinPaymentIAPResponse
143
+ > {
144
+ private appAccountToken: string | undefined;
145
+ constructor(readonly productId: string, readonly appStoreProductId: string) {
146
+ super();
147
+ }
148
+ async placeOrder(
149
+ params: IPlaceOrderJoliCoinIAPParamas
150
+ ): Promise<StandardResponse<IPlaceOrderResponse<IJoliCoinProductInfo>, unknown>> {
151
+ console.log('----start place order for ios----');
152
+ const { response } = await fetch<StandardResponse<{ iapToken: string }>>('/api/orders/iap-init', {
153
+ method: 'GET',
154
+ appendHostCookie: true
155
+ });
156
+ const { data, code } = response ?? {};
157
+ console.log('----iap init----', data, code);
158
+ if (data.code !== 'SUCCESS') {
159
+ throw createPaymentInternalError(
160
+ 'iap-init failed',
161
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
162
+ );
163
+ }
164
+ this.appAccountToken = data?.data?.iapToken;
165
+ return {
166
+ code: 'SUCCESS' as ResponseType,
167
+ message: 'placeorder jolicoin success in native'
168
+ };
169
+ }
170
+
171
+ generatePaymentContext = (): IJolicoinPaymentIAPContext => {
172
+ return {
173
+ type: 'JOLI_COIN_IAP_CONTEXT',
174
+ state: 'TO_VALIDATE',
175
+ productId: this.productId,
176
+ appStoreProductId: this.appStoreProductId
177
+ };
178
+ };
179
+
180
+ pay = async (): Promise<StandardResponse<{ totalAmount: string }>> => {
181
+ return await payInApp({
182
+ appStoreProductId: this.appStoreProductId,
183
+ appAccountToken: this.appAccountToken
184
+ });
185
+ };
186
+ }
187
+
188
+ const IAPPaymentRegisterMap = {
189
+ android: JolicoinIAPAndroidPaymentResiter,
190
+ ios: JolicoinIAPIOSPaymentResiter
191
+ } as const;
192
+
193
+ type JolicoinIAPPaymentResiter = JolicoinIAPAndroidPaymentResiter | JolicoinIAPIOSPaymentResiter;
194
+ export const createJolicoinIAPPaymentHandler = () => {
195
+ const productPaymentMap = new Map<string, JolicoinIAPPaymentResiter>();
196
+ return async (params: { productId: string; appStoreProductId: string }) => {
197
+ const { productId, appStoreProductId } = params;
198
+ let instance = productPaymentMap.get(productId);
199
+ if (!instance) {
200
+ const Register = IAPPaymentRegisterMap[context.platform as keyof typeof IAPPaymentRegisterMap];
201
+ if (!Register) {
202
+ throw createPaymentInternalError(
203
+ 'platform not supported',
204
+ PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed
205
+ );
206
+ }
207
+ instance = new Register(productId, appStoreProductId);
208
+ productPaymentMap.set(productId, instance);
209
+ }
210
+
211
+ const { code, message } = await instance.startPayment({ productId, appStoreProductId });
212
+ if (code !== 'SUCCESS') {
213
+ throw createPaymentError(message, PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed);
214
+ }
215
+ return {
216
+ code: 'SUCCESS' as ResponseType,
217
+ message: 'jolicoin payment success'
218
+ };
219
+ };
220
+ };
@@ -20,6 +20,7 @@ const parseFrequency = (data: string) => {
20
20
  export const checkUseModalFrequency = async (config: { dailyMaxPopUps: number; minInterval: number }) => {
21
21
  const { dailyMaxPopUps, minInterval } = config;
22
22
  const res = (await getGlobalStorage('joli_coin_use_modal_frequency')) as StandardResponse<string>;
23
+ console.log('checkUseModalFrequency', res.data);
23
24
  if (!res.data) {
24
25
  return true;
25
26
  }
@@ -53,7 +54,7 @@ export const updateUseModalFrequency = async () => {
53
54
  export const checkPaymentFrequency = async (config: { dailyMaxPopUps: number; minInterval: number }) => {
54
55
  const { dailyMaxPopUps, minInterval } = config;
55
56
  const res = (await getGlobalStorage('joli_coin_payment_frequency')) as StandardResponse<string>;
56
-
57
+ console.log('checkPaymentFrequency', res.data);
57
58
  if (!res.data) {
58
59
  return true;
59
60
  }
@@ -14,7 +14,7 @@ import {
14
14
  UseModalFrequencyEventName,
15
15
  IUseModalFrequencyConfig
16
16
  } from '@/common/rewards/reward-emitter';
17
- import { createConfirmJolicoinModal, createPaymentJolicoinModal } from '@jolibox/ui';
17
+ import { createConfirmJolicoinModal, createPaymentJolicoinModal, createToast } from '@jolibox/ui';
18
18
  import { innerFetch as fetch } from '../network';
19
19
  import { StandardResponse } from '@jolibox/types';
20
20
  import { context } from '@/common/context';
@@ -28,6 +28,10 @@ import {
28
28
  updatePaymentFrequency
29
29
  } from './check-frequency';
30
30
  import { paymentHelper } from '../payment';
31
+ import { createLoading } from '@jolibox/ui';
32
+ import { canIUseNative } from '../api/base';
33
+ import { applyNative } from '@jolibox/native-bridge';
34
+ import { isUndefinedOrNull } from '@jolibox/common';
31
35
 
32
36
  const modalUseFrequencyConfig = createEventPromiseHandler<
33
37
  IUseModalFrequencyConfig,
@@ -36,6 +40,8 @@ const modalUseFrequencyConfig = createEventPromiseHandler<
36
40
 
37
41
  modalUseFrequencyConfig.getData();
38
42
 
43
+ const loading = createLoading();
44
+
39
45
  /**
40
46
  * update config
41
47
  */
@@ -67,9 +73,13 @@ const updateAutoDeductConfig = async (enabled: boolean): Promise<void> => {
67
73
  rewardsEmitter.on(UseModalEventName, async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IUseModalEvent) => {
68
74
  if (type === 'ADS-JOLI_COIN') {
69
75
  //TODO
70
- console.log('use modal show by frequency');
76
+ await loading.show({
77
+ duration: 3000
78
+ });
71
79
  const config = await modalUseFrequencyConfig.getData();
72
80
  const canShowUseModal = await checkUseModalFrequency(config.joliCoinUseAndCharge.useJolicoin);
81
+ console.log('use modal show by frequency', canShowUseModal);
82
+ loading.hide();
73
83
  if (!canShowUseModal) {
74
84
  // return by frequency control
75
85
  rewardsEmitter.emit(UseModalResultEventName, { useModalResult: 'CONFIRM' });
@@ -87,7 +97,7 @@ rewardsEmitter.on(UseModalEventName, async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN',
87
97
  text: params.confirmButtonText,
88
98
  onPress: () => {
89
99
  rewardsEmitter.emit(UseModalResultEventName, { useModalResult: 'CONFIRM' });
90
- modal.destroy();
100
+ modal.hide();
91
101
  }
92
102
  },
93
103
  cancel: {
@@ -96,7 +106,7 @@ rewardsEmitter.on(UseModalEventName, async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN',
96
106
  rewardsEmitter.emit(UseModalResultEventName, {
97
107
  useModalResult: (type ?? '') === 'CANCEL' ? 'CANCEL' : 'FAILED'
98
108
  });
99
- modal.destroy();
109
+ modal.hide();
100
110
  }
101
111
  },
102
112
  onEnableDeductChanged: async (enabled: boolean) => {
@@ -115,11 +125,24 @@ rewardsEmitter.on(
115
125
  InvokePaymentEventName,
116
126
  async (type: 'JOLI_COIN' | 'ADS-JOLI_COIN', params: IInvokePaymentEvent) => {
117
127
  try {
128
+ // TODO: temp remove it for dev
129
+ if (!canIUseNative('requestPaymentSync:paymentBody:appStoreProductId')) {
130
+ //TODO: show Toast
131
+ console.info('requestPaymentSync:paymentBody:appStoreProductId not supported');
132
+ setTimeout(() => {
133
+ rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
134
+ }, 0);
135
+ return;
136
+ }
118
137
  // handle showup frequecy
119
138
  if (type === 'ADS-JOLI_COIN') {
120
- //
139
+ await loading.show({
140
+ duration: 3000
141
+ });
121
142
  const config = await modalUseFrequencyConfig.getData();
122
143
  const canShowPaymentModal = await checkPaymentFrequency(config.joliCoinUseAndCharge.charge);
144
+ console.log('use payment show by frequency', canShowPaymentModal);
145
+ loading.hide();
123
146
  if (!canShowPaymentModal) {
124
147
  // return by frequency control
125
148
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS' });
@@ -128,7 +151,15 @@ rewardsEmitter.on(
128
151
  console.log('show by frequency');
129
152
  }
130
153
 
154
+ /**
155
+ * TODO: need merge totalAmountStr from native
156
+ */
157
+ await loading.show({
158
+ duration: 3000
159
+ });
131
160
  const balenceDetails = await getBalenceDetails();
161
+ loading.hide();
162
+
132
163
  if (!balenceDetails) {
133
164
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
134
165
  return;
@@ -137,15 +168,17 @@ rewardsEmitter.on(
137
168
  data: {
138
169
  userJolicoin: params.userJoliCoin,
139
170
  joliCoinQuantity: params.joliCoinQuantity,
140
- paymentChoices: balenceDetails.paymentChoices,
171
+ paymentChoices:
172
+ balenceDetails.paymentChoices?.map((choice) => ({
173
+ ...choice,
174
+ totalAmountStr: choice.totalAmountStr ?? ''
175
+ })) ?? [],
141
176
  enableAutoDeduct: balenceDetails.enableAutoDeduct
142
177
  },
143
178
  buttons: {
144
179
  confirm: {
145
180
  text: params.confirmButtonText,
146
181
  onPress: async (productId: string) => {
147
- // TODO: native payment
148
- // await invokeNativePayment(productId);
149
182
  if (!context.hostUserInfo?.isLogin) {
150
183
  const { data } = await login();
151
184
  if (!data?.isLogin) {
@@ -156,19 +189,35 @@ rewardsEmitter.on(
156
189
  const balenceDetails = await getBalenceDetails();
157
190
  if ((balenceDetails?.balance ?? 0) >= params.joliCoinQuantity) {
158
191
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS' });
159
- modal.destroy();
192
+ modal.hide();
160
193
  return;
161
194
  }
162
195
  }
163
196
  console.log('invokeNativePayment', productId);
164
- const { code } = await paymentHelper.invokePayment('JOLI_COIN', productId);
165
- if (code !== 'SUCCESS') {
197
+ const appStoreProductId = balenceDetails?.paymentChoices?.find(
198
+ (choice) => choice.productId === productId
199
+ )?.appStoreProductId;
200
+
201
+ if (!appStoreProductId) {
166
202
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'FAILED' });
167
- modal.destroy();
203
+ modal.hide();
204
+ return;
205
+ }
206
+ await loading.show({
207
+ duration: 3000
208
+ });
209
+ const { code } = await paymentHelper.invokePayment('JOLI_COIN_IAP', {
210
+ productId,
211
+ appStoreProductId
212
+ });
213
+ loading.hide();
214
+ if (code !== 'SUCCESS') {
215
+ /** add timeout for google panel closed */
216
+ console.info('[JoliboxSDK] payment failed in payment.invokePaymet');
168
217
  return;
169
218
  }
170
219
  rewardsEmitter.emit(PaymentResultEventName, { paymentResult: 'SUCCESS' });
171
- modal.destroy();
220
+ modal.hide();
172
221
  }
173
222
  },
174
223
  cancel: {
@@ -177,7 +226,7 @@ rewardsEmitter.on(
177
226
  rewardsEmitter.emit(PaymentResultEventName, {
178
227
  paymentResult: (type ?? '') === 'CANCEL' ? 'CANCEL' : 'FAILED'
179
228
  });
180
- modal.destroy();
229
+ modal.hide();
181
230
  }
182
231
  },
183
232
  onEnableDeductChanged: async (enabled: boolean) => {
@@ -194,6 +243,23 @@ rewardsEmitter.on(
194
243
  }
195
244
  );
196
245
 
246
+ const mergeResponseData = (
247
+ responseData: { paymentChoices: IPaymentChoice[] },
248
+ data: { [appStoreProductId: string]: { price: string } }
249
+ ) => {
250
+ Object.keys(data).forEach((key) => {
251
+ const choice = responseData.paymentChoices.find((choice) => choice.appStoreProductId === key);
252
+ if (choice) {
253
+ choice.totalAmountStr = data[key].price;
254
+ }
255
+ });
256
+
257
+ responseData.paymentChoices = responseData.paymentChoices.filter(
258
+ (choice) => !isUndefinedOrNull(choice.totalAmountStr)
259
+ ) as IPaymentChoice[];
260
+ console.info('----mergeResponseData-----', responseData.paymentChoices);
261
+ };
262
+
197
263
  const getBalenceDetails = async (): Promise<
198
264
  | {
199
265
  balance: number;
@@ -214,5 +280,17 @@ const getBalenceDetails = async (): Promise<
214
280
  responseType: 'json'
215
281
  });
216
282
 
283
+ console.info('getBalenceDetails', response);
284
+ const { data } = await applyNative('requestProductDetailsAsync', {
285
+ appStoreProductIds:
286
+ response.data?.data?.paymentChoices
287
+ ?.filter((choice) => typeof choice.appStoreProductId === 'string')
288
+ .map((choice) => choice.appStoreProductId as string) ?? []
289
+ });
290
+
291
+ if (response.data?.data && data) {
292
+ mergeResponseData(response.data?.data, data);
293
+ }
294
+ console.info('productDetails', response.data?.data);
217
295
  return response.data?.data;
218
296
  };
@@ -108,7 +108,7 @@ export async function openRetentionSchema() {
108
108
  break;
109
109
  case 'dismiss':
110
110
  quitResultDeffer.resolve(true);
111
- modal.hide();
111
+ modal.destroy();
112
112
  break;
113
113
  case 'navigate':
114
114
  // TODO: 跳转游戏
@@ -121,7 +121,7 @@ export async function openRetentionSchema() {
121
121
  } else {
122
122
  quitResultDeffer.resolve(true);
123
123
  }
124
- modal.hide();
124
+ modal.destroy();
125
125
  break;
126
126
  default:
127
127
  // 关闭弹框,留在当前游戏