import { innerFetch as fetch } from '@/native/network'; import { BasePaymentRegister, createPaymentError, createPaymentInternalError } from './base'; import { PaymentErrorCodeMap, IPlaceOrderResponse } from './type'; import { ResponseType, StandardResponse } from '@jolibox/types'; import { applyNative, onNative } from '@jolibox/native-bridge'; import { Deferred, isNumber, platform } from '@jolibox/common'; import { context } from '@/common/context'; import { createToast } from '@jolibox/ui'; import { IPaymentIAPFailedStatusMap } from './const'; export interface IPlaceOrderJoliCoinIAPParamas { productId: string; appStoreProductId: string; } export interface IJoliCoinProductInfo { quantity: number; } interface IJolicoinPaymentIAPResponse { totalAmount: string; } export interface IJolicoinPaymentIAPContext { type: 'JOLI_COIN_IAP_CONTEXT'; state: 'TO_CHECKOUT' | 'TO_VALIDATE'; productId: string; appStoreProductId: string; } const pendingPayments = new Map>>(); onNative('onPaymentStateChange', (data) => { console.info('onPaymentStateChange', data); const { orderUUID, status, totalAmount, orderResponse } = data; const deferred = pendingPayments.get(orderUUID); if (!deferred) { return; } if (status === 'SUCCESS') { deferred.resolve({ code: 'SUCCESS' as ResponseType, message: 'jolicoin payment success', data: { totalAmount } }); } else { if (status === 'FAILED') { if (isNumber(orderResponse?.errNo) && orderResponse?.errNo !== 0) { throw createPaymentInternalError( `jolicoin payment failed: ${orderResponse?.errMsg}`, PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed ); } } const failedStatus = IPaymentIAPFailedStatusMap[status]; if (failedStatus) { createToast(`{slot-error} {slot-i18n-jolicoin.${failedStatus}}`, { position: 'center', duration: 3000 }); } deferred.resolve({ code: 'FAILED' as ResponseType, message: 'jolicoin payment failed' }); } // Clean up map pendingPayments.delete(orderUUID); }); const purchaseGem = async (params: { appStoreProductId: string; appAccountToken?: string }) => { const deferred = new Deferred>(); let targetOrderUUID: string | undefined; try { const response = await applyNative('requestPaymentSync', { paymentBody: { appStoreProductId: params.appStoreProductId, appAccountToken: params.appAccountToken } }); targetOrderUUID = response.data?.orderUUID; console.info('---purchaseGem---', response); if (!targetOrderUUID) { throw createPaymentInternalError( 'orderUUID is null', PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed ); } pendingPayments.set(targetOrderUUID, deferred); } catch (e) { throw createPaymentInternalError( JSON.stringify(e) ?? 'jolicoin payment failed', PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed ); } return deferred.promise; }; class JolicoinIAPAndroidPaymentResiter extends BasePaymentRegister< IPlaceOrderJoliCoinIAPParamas, IJoliCoinProductInfo, IJolicoinPaymentIAPResponse > { constructor(readonly productId: string, readonly appStoreProductId: string) { super(); } async placeOrder( params: IPlaceOrderJoliCoinIAPParamas ): Promise, unknown>> { return { code: 'SUCCESS' as ResponseType, message: 'placeorder jolicoin success in native' }; } generatePaymentContext = (): IJolicoinPaymentIAPContext => { return { type: 'JOLI_COIN_IAP_CONTEXT', state: 'TO_VALIDATE', productId: this.productId, appStoreProductId: this.appStoreProductId }; }; pay = async (): Promise> => { return await purchaseGem({ appStoreProductId: this.appStoreProductId }); }; } class JolicoinIAPIOSPaymentResiter extends BasePaymentRegister< IPlaceOrderJoliCoinIAPParamas, IJoliCoinProductInfo, IJolicoinPaymentIAPResponse > { private appAccountToken: string | undefined; constructor(readonly productId: string, readonly appStoreProductId: string) { super(); } async placeOrder( params: IPlaceOrderJoliCoinIAPParamas ): Promise, unknown>> { console.log('----start place order for ios----'); const { response } = await fetch>('/api/orders/iap-init', { method: 'GET', appendHostCookie: true }); const { data, code } = response ?? {}; console.log('----iap init----', data, code); if (data.code !== 'SUCCESS') { throw createPaymentInternalError( 'iap-init failed', PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed ); } this.appAccountToken = data?.data?.iapToken; return { code: 'SUCCESS' as ResponseType, message: 'placeorder jolicoin success in native' }; } generatePaymentContext = (): IJolicoinPaymentIAPContext => { return { type: 'JOLI_COIN_IAP_CONTEXT', state: 'TO_VALIDATE', productId: this.productId, appStoreProductId: this.appStoreProductId }; }; pay = async (): Promise> => { return await purchaseGem({ appStoreProductId: this.appStoreProductId, appAccountToken: this.appAccountToken }); }; } const IAPPaymentRegisterMap = { android: JolicoinIAPAndroidPaymentResiter, ios: JolicoinIAPIOSPaymentResiter } as const; type JolicoinIAPPaymentResiter = JolicoinIAPAndroidPaymentResiter | JolicoinIAPIOSPaymentResiter; export const createJolicoinIAPPaymentHandler = () => { const productPaymentMap = new Map(); return async (params: { productId: string; appStoreProductId: string }) => { const { productId, appStoreProductId } = params; let instance = productPaymentMap.get(productId); if (!instance) { const Register = IAPPaymentRegisterMap[context.platform as keyof typeof IAPPaymentRegisterMap]; if (!Register) { throw createPaymentInternalError( 'platform not supported', PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed ); } instance = new Register(productId, appStoreProductId); productPaymentMap.set(productId, instance); } const { code, message } = await instance.startPayment({ productId, appStoreProductId }); if (code !== 'SUCCESS') { throw createPaymentError(message, PaymentErrorCodeMap.JolicoinPlaceOrderRequestFailed); } return { code: 'SUCCESS' as ResponseType, message: 'jolicoin payment success' }; }; };