import { StandardResponse } from '@jolibox/types'; import type { IPlaceOrderJoliCoinParamas, IJoliCoinProductInfo, IJolicoinPaymentContext } from './joli-coin'; import type { IPlaceOrderJoliCoinIAPParamas, IJolicoinPaymentIAPContext } from './jolicoin-iap'; import type { IPlaceOrderResponse } from './type'; import { PaymentErrorCodeMap } from './type'; type PaymentStatus = 'INIT' | 'PENDING' | 'SUCCESS' | 'FAILED'; import { UserPaymentError, InternalPaymentError } from '@jolibox/common'; import { context } from '@/common/context'; import { innerFetch as fetch } from '@/native/network'; type IPlaceOrderParams = IPlaceOrderJoliCoinParamas | IPlaceOrderJoliCoinIAPParamas; type IOrderProductInfo = IJoliCoinProductInfo | IJolicoinPaymentIAPContext; type IOrderPaymentContext = IJolicoinPaymentContext | IJolicoinPaymentIAPContext; const createPaymentErrorFactory = () => { const [createPaymentError, createPaymentInternalError] = [UserPaymentError, InternalPaymentError].map( (ctor) => { return (errMsg: string, errNo: number, extra?: Record) => { return new ctor(errMsg, errNo, extra); }; } ); return { createPaymentError, createPaymentInternalError }; }; const { createPaymentError, createPaymentInternalError } = createPaymentErrorFactory(); export { createPaymentError, createPaymentInternalError }; const CALLBACK_HOST = context.testMode ? 'https://stg-game.jolibox.com' : 'https://game.jolibox.com'; export abstract class BasePaymentRegister< T extends IPlaceOrderParams, E extends IOrderProductInfo, R = void > { private _orderId: string | null = null; private _orderStatus: PaymentStatus = 'INIT'; startPayment = async (params: T): Promise> => { if (this._orderId) { await this.cancelOrder(this.orderId as string); } const { code, message } = await this.placeOrder(params); if (code !== 'SUCCESS') { throw createPaymentError(message, PaymentErrorCodeMap.PlaceOrderFailed); } // invoke native payment; return await this.pay(); // 回调页 }; cancelOrder = async (orderId: string) => { const { response } = await fetch(`/api/orders/${orderId}/cancel`, { method: 'POST', data: {} }); if (response.code !== 'SUCCCESS') { throw createPaymentError( response.message ?? response.errMsg ?? '', PaymentErrorCodeMap.CancelOrderFailed ); } }; protected generateCallbackUrl = () => { // Set or replace gameId and joliSource parameters const originalSearch = new URLSearchParams(''); originalSearch.set('utm_source', context.deviceInfo.platform); originalSearch.set( 'joliSource', context.encodeJoliSourceQuery({ ...this.generatePaymentContext() }) ); return `${CALLBACK_HOST}//payment/callback?${originalSearch.toString()}`; }; abstract placeOrder(params: T): Promise>>; abstract generatePaymentContext(): IOrderPaymentContext; abstract pay(): Promise>; get orderId(): string | null { return this._orderId; } get status(): PaymentStatus { return this._orderStatus; } }