From bc6d3d7712350a2ebb8d350aa9f020aed83f61b7 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Mon, 11 Nov 2024 08:37:45 +0800 Subject: [PATCH 01/13] Integrate the Google Play Billing Library into engine --- cocos/core/platform/billing/billing.ts | 804 +++++++++ cocos/core/platform/index.ts | 11 + cocos/native-binding/index.ts | 345 ++++ native/CMakeLists.txt | 8 + .../manual/jsb_google_play_billing.cpp | 1523 +++++++++++++++++ .../bindings/manual/jsb_module_register.cpp | 6 + .../java/src/com/cocos/lib/CocosBilling.java | 471 +++++ .../src/com/cocos/lib/CocosBillingHelper.java | 207 +++ .../android/libcocos2dx/AndroidManifest.xml | 1 + .../platform/android/libcocos2dx/build.gradle | 7 +- .../google_play/billing/JniBillingHelper.cpp | 583 +++++++ .../google_play/billing/JniBillingHelper.h | 113 ++ .../modules/google_play/billing/billing.cpp | 104 ++ .../modules/google_play/billing/billing.h | 197 +++ .../platform/java/jni/JniCocosBilling.cpp | 129 ++ 15 files changed, 4507 insertions(+), 2 deletions(-) create mode 100644 cocos/core/platform/billing/billing.ts create mode 100644 native/cocos/bindings/manual/jsb_google_play_billing.cpp create mode 100644 native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java create mode 100644 native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java create mode 100644 native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp create mode 100644 native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h create mode 100644 native/cocos/platform/android/modules/google_play/billing/billing.cpp create mode 100644 native/cocos/platform/android/modules/google_play/billing/billing.h create mode 100644 native/cocos/platform/java/jni/JniCocosBilling.cpp diff --git a/cocos/core/platform/billing/billing.ts b/cocos/core/platform/billing/billing.ts new file mode 100644 index 00000000000..72a4fd3b935 --- /dev/null +++ b/cocos/core/platform/billing/billing.ts @@ -0,0 +1,804 @@ +import { JSB } from 'internal:constants'; +import { native } from '../../../native-binding'; +import { EventTarget } from '../../event'; +import { ccenum } from '../../value-types/enum'; + +declare const jsb: any; +/** + * @en Google play billing event type + * @zh 输入事件类型 + */ +export enum BillingEventType { + /** + * @en + * Called to notify that setup is complete. + * + * @zh + * 当安装已经完成时触发。 + */ + BILLING_SETUP_FINISHED = 'billing_setup_finished', + /** + * @en + * Called to notify that the connection to the billing service was lost. + * + * @zh + * 当Billing服务连接断开时触发。 + */ + BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', + /** + * @en + * Listen to this event to get notifications of purchase updates. + * + * @zh + * 监听这个事件可以获取购买更新。 + */ + PURCHASES_UPDATED = 'purchases_updated', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 查询产品详细信息操作完成时触发。 + */ + PRODUCT_DETAILS_RESPONSE = 'product_details_response', + /** + * @en + * Called to notify that the query purchases operation has finished. + * + * @zh + * 查询购买操作完成时触发。 + */ + QUERY_PURCHASES_RESPONSE = 'query_purchases_response', + /** + * @en + * Called to notify that a consume operation has finished. + * + * @zh + * 消费操作完成时触发。 + */ + CONSUME_RESPONSE = 'consume_response', + /** + * @en + * Called to notify that an acknowledge purchase operation has finished. + * + * @zh + * 确认购买操作完成时触发。 + */ + ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', + /** + * @en + * Called to notify when the get billing config flow has finished. + * + * @zh + * 获取Billing配置流程完成时触发。 + */ + BILLING_CONFIG_RESPONSE = 'billing_config_response', + /** + * @en + * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 当通知查询产品详细信息操作已完成时调用。 + */ + EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', + /** + * @en + * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 当通知查询产品详细信息操作已完成时调用。 + */ + EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', + /** + * @en + * Called to notify that the alternative billing only dialog flow is finished. + * + * @zh + * 当仅替代Billing对话流程已完成时触发。 + */ + ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 当通知查询产品详细信息操作已完成时调用。 + */ + EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', + /** + * @en + * Called to notify when the in-app messaging flow has finished. + * + * @zh + * 当应用内消息流程完成时触发。 + */ + IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', + +} +ccenum(BillingEventType); + +/** + * @en + * Supported Product types. + * + * @zh + * 支持的产品类型。 + */ +export enum ProductType { + /** + * @en + * A Product type for Android apps in-app products. + * + * @zh + * Android 应用内产品的产品类型。 + */ + INAPP = 'inapp', + /** + * @en + * A Product type for Android apps subscriptions. + * + * @zh + * Android 应用程序订阅的产品类型。 + */ + SUBS = 'subs' +} + +/** + * @en + * Possible response codes. + * + * @zh + * 可能的响应代码。 + */ +export enum BillingResponseCode { + /** + * @en + * This field is deprecated. + * See SERVICE_UNAVAILABLE which will be used instead of this code. + * + * @zh + * 这个字段已经废弃。 + * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 + */ + SERVICE_TIMEOUT = -3, + /** + * @en + * The requested feature is not supported by the Play Store on the current device. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + FEATURE_NOT_SUPPORTED = -2, + /** + * @en + * The app is not connected to the Play Store service via the Google Play Billing Library. + * + * @zh + * 该应用未通过 Google Play 结算库连接到 Play Store 服务。 + */ + SERVICE_DISCONNECTED = -1, + /** + * @en + * Success. + * + * @zh + * 成功。 + */ + OK = 0, + /** + * @en + * Transaction was canceled by the user. + * + * @zh + * 交易已被用户取消。 + */ + USER_CANCELED = 1, + /** + * @en + * The service is currently unavailable. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + SERVICE_UNAVAILABLE = 2, + /** + * @en + * A user billing error occurred during processing. + * + * @zh + * 处理过程中出现用户计费错误。 + */ + BILLING_UNAVAILABLE = 3, + /** + * @en + * The requested product is not available for purchase. + * + * @zh + * 所请求的产品无法购买。 + */ + ITEM_UNAVAILABLE = 4, + /** + * @en + * Error resulting from incorrect usage of the API. + * + * @zh + * 由于错误使用 API 而导致的错误。 + */ + DEVELOPER_ERROR = 5, + /** + * @en + * Fatal error during the API action. + * + * @zh + * API 操作期间发生致命错误。 + */ + ERROR = 6, + /** + * @en + * The purchase failed because the item is already owned. + * + * @zh + * 购买失败,因为该物品已被拥有。 + */ + ITEM_ALREADY_OWNED = 7, + /** + * @en + * Requested action on the item failed since it is not owned by the user. + * + * @zh + * 由于该项目不属于用户,因此对该项目请求的操作失败。 + */ + ITEM_NOT_OWNED = 8, + /** + * @en + * A network error occurred during the operation. + * + * @zh + * 操作期间发生网络错误。 + */ + NETWORK_ERROR = 12, +} + +/** + * @en + * Recurrence mode of the pricing phase. + * + * @zh + * 定价阶段的复现模式。 + */ +export enum RecurrenceMode { + /** + * @en + * The billing plan payment recurs for infinite billing periods unless cancelled. + * + * @zh + * 除非取消,否则计费计划付款将无限期地重复。 + */ + INFINITE_RECURRING = 1, + /** + * @en + * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. + * + * @zh + * 计费计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 + */ + FINITE_RECURRING = 2, + /** + * @en + * The billing plan payment is a one time charge that does not repeat. + * + * @zh + * 计费计划付款是一次性费用,不会重复。 + */ + NON_RECURRING = 3, +} + +/** + * @en + * Connection state of billing client. + * + * @zh + * BillingClient的连接状态 + */ +export enum ConnectionState { + /** + * @en + * This client was not yet connected to billing service or was already closed. + * + * @zh + * 此客户端尚未连接到Billing服务或已关闭。 + */ + DISCONNECTED = 0, + /** + * @en + * This client is currently in process of connecting to billing service. + * + * @zh + * 此客户端目前正在连接到Billing服务。 + */ + CONNECTING = 1, + /** + * @en + * This client is currently connected to billing service. + * + * @zh + * 此客户端当前已连接到Billing服务。 + */ + CONNECTED = 2, + /** + * @en + * This client was already closed and shouldn't be used again. + * + * @zh + * 该客户端已关闭,不应再次使用。 + */ + CLOSED = 3, +} + +/** + * @en + * Features/capabilities supported by isFeatureSupported. + * + * @zh + * 支持的特性/能力isFeatureSupported。 + */ +export enum FeatureType { + /** + * @en + * Alternative billing only. + * + * @zh + * 仅限替代Billing。 + */ + ALTERNATIVE_BILLING_ONLY = 'jjj', + /** + * @en + * Get billing config. + * + * @zh + * 获取计费配置。。 + */ + BILLING_CONFIG = 'ggg', + /** + * @en + * Play billing library support for external offer. + * + * @zh + * Play billing库支持外部报价。 + */ + EXTERNAL_OFFER = 'kkk', + /** + * @en + * Show in-app messages. + * + * @zh + * 显示应用内消息。 + */ + IN_APP_MESSAGING = 'bbb', + /** + * @en + * Launch a price change confirmation flow. + * + * @zh + * 启动价格变动确认流程。 + */ + PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', + /** + * @en + * Play billing library support for querying and purchasing. + * + * @zh + * Play Billing库支持查询、购买。 + */ + PRODUCT_DETAILS = 'fff', + /** + * @en + * Purchase/query for subscriptions. + * + * @zh + * 购买/查询订阅。 + */ + SUBSCRIPTIONS = 'subscriptions', + /** + * @en + * Subscriptions update/replace. + * + * @zh + * 订阅更新/替换。 + */ + UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', +} + +/** + * @en + * Possible purchase states. + * + * @zh + * 可能的购买状态。 + */ +export enum PurchaseState { + /** + * @en + * Purchase is pending and not yet completed to be processed by your app. + * + * @zh + * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 + */ + PENDING = 2, + /** + * @en + * Purchase is completed.. + * + * @zh + * 购买完成。 + */ + PURCHASED = 1, + /** + * @en + * Purchase with unknown state. + * + * @zh + * 未知状态 + */ + UNSPECIFIED_STATE = 0, +} + +/** + * @en + * Possible response codes. + * + * @zh + * InAppMessage可能的影响代码。 + */ +export enum InAppMessageResponseCode { + /** + * @en + * The flow has finished and there is no action needed from developers. + * + * @zh + * 流程已完成,开发人员无需采取任何行动。 + */ + NO_ACTION_NEEDED = 0, + /** + * @en + * The subscription status changed. + * + * @zh + * 订阅状态已改变。 + */ + SUBSCRIPTION_STATUS_UPDATED = 1, +} + +interface BillingEventMap { + [BillingEventType.BILLING_SETUP_FINISHED]: (result: native.BillingResult) => void, + [BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, + [BillingEventType.PRODUCT_DETAILS_RESPONSE]: (result: native.BillingResult, + productDetailsList: native.ProductDetails[]) => void, + [BillingEventType.PURCHASES_UPDATED]: (result: native.BillingResult, + purchases: native.Purchase[]) => void, + [BillingEventType.CONSUME_RESPONSE]: (result: native.BillingResult, + purchaseToken: string) => void, + [BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: native.BillingResult, + purchases: native.Purchase[]) => void, + [BillingEventType.BILLING_CONFIG_RESPONSE]: (result: native.BillingResult, config: native.BillingConfig) => void + [BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails) => void + [BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails) => void + [BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void + [BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void + [BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: native.BillingResult) => void + [BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: native.BillingResult) => void + [BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: native.InAppMessageResult) => void +} + +export class GooglePlayBilling { + private _eventTarget: EventTarget = new EventTarget(); + constructor () { + if (!JSB || !jsb.billing) { + return; + } + jsb.onBillingSetupFinished = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.BILLING_SETUP_FINISHED, result); + }; + + jsb.onBillingServiceDisconnected = (): void => { + this._eventTarget.emit(BillingEventType.BILLING_SERVICE_DISCONNECTED); + }; + + jsb.onProductDetailsResponse = ( + result: native.BillingResult, + productDetailsList: native.ProductDetails[], + ): void => { + this._eventTarget.emit(BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); + }; + + jsb.onPurchasesUpdated = ( + result: native.BillingResult, + purchaseList: native.Purchase[], + ): void => { + this._eventTarget.emit(BillingEventType.PURCHASES_UPDATED, result, purchaseList); + }; + + jsb.onConsumeResponse = ( + result: native.BillingResult, + purchaseToken: string, + ): void => { + this._eventTarget.emit(BillingEventType.CONSUME_RESPONSE, result, purchaseToken); + }; + + jsb.onAcknowledgePurchaseResponse = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); + }; + + jsb.onQueryPurchasesResponse = ( + result: native.BillingResult, + purchaseList: native.Purchase[], + ): void => { + this._eventTarget.emit(BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); + }; + + jsb.onBillingConfigResponse = (result: native.BillingResult, config: native.BillingConfig): void => { + this._eventTarget.emit(BillingEventType.BILLING_CONFIG_RESPONSE, result, config); + }; + + jsb.onAlternativeBillingOnlyTokenResponse = (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails): void => { + this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, alternativeBillingOnlyReportingDetails); + }; + + jsb.onExternalOfferReportingDetailsResponse = (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails): void => { + this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, externalOfferReportingDetails); + }; + + jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); + }; + + jsb.onExternalOfferAvailabilityResponse = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); + }; + + jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onExternalOfferInformationDialogResponse = (result: native.BillingResult): void => { + this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onInAppMessageResponse = (result: native.InAppMessageResult): void => { + this._eventTarget.emit(BillingEventType.IN_APP_MESSAGE_RESPONSE, result); + }; + } + + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + public startConnection (): void { + jsb.billing?.startConnection(); + } + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + public endConnection (): void { + jsb.billing?.endConnection(); + } + + /** + * @en Get the current billing client connection state. + * @zh 获取当前计费客户端连接状态。 + */ + public getConnectionState (): number { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.getConnectionState(); + } + return 0; + } + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + public isReady (): boolean { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isReady(); + } + return false; + } + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + */ + public queryProductDetailsParams (productId: string[] | string, productType: ProductType): void { + if (productId instanceof Array) { + jsb.billing?.queryProductDetailsParams(productId, productType); + } else { + const productIdList: string[] = []; + productIdList.push(productId); + jsb.billing?.queryProductDetailsParams(productIdList, productType); + } + } + + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + */ + public launchBillingFlow (productDetails: native.ProductDetails[] | native.ProductDetails, selectedOfferToken: string | null): void { + if (productDetails instanceof Array) { + jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); + } else { + const productDetailsList: native.ProductDetails[] = []; + productDetailsList.push(productDetails); + jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); + } + } + + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + */ + public consumePurchases (purchase: native.Purchase[] | native.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.consumePurchases(purchase); + } else { + const purchaseList: native.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.consumePurchases(purchaseList); + } + } + + /** + * @en Acknowledges in-app purchases. + * @zh 确认应用内购买。 + */ + public acknowledgePurchase (purchase: native.Purchase[] | native.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.acknowledgePurchase(purchase); + } else { + const purchaseList: native.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.acknowledgePurchase(purchase); + } + } + + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + */ + public queryPurchasesAsync (productType: ProductType): void { + jsb.billing?.queryPurchasesAsync(productType); + } + + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + public getBillingConfigAsync (): void { + jsb.billing?.getBillingConfigAsync(); + } + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + */ + public createAlternativeBillingOnlyReportingDetailsAsync (): void { + jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); + } + + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + */ + public isAlternativeBillingOnlyAvailableAsync (): void { + jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); + } + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + public createExternalOfferReportingDetailsAsync (): void { + jsb.billing?.createExternalOfferReportingDetailsAsync(); + } + + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + public isExternalOfferAvailableAsync (): void { + jsb.billing?.isExternalOfferAvailableAsync(); + } + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + */ + public isFeatureSupported (feature: string): native.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isFeatureSupported(feature); + } + return null; + } + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + public showAlternativeBillingOnlyInformationDialog (): native.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showAlternativeBillingOnlyInformationDialog(); + } + return null; + } + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + public showExternalOfferInformationDialog (): native.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showExternalOfferInformationDialog(); + } + return null; + } + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + public showInAppMessages (): native.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showInAppMessages(); + } + return null; + } + + public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.on(type, callback, target); + return callback; + } + public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.once(type, callback, target); + return callback; + } + public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { + this._eventTarget.off(eventType, callback, target); + } +} + +/** + * @en + * The singleton of the Input class, this singleton manages all events of input. include: touch, mouse, accelerometer, gamepad, handle, hmd and keyboard. + * + * @zh + * google play的blling支付模块的接口。 + * + * @example + */ +export const googlePlayBilling = new GooglePlayBilling(); diff --git a/cocos/core/platform/index.ts b/cocos/core/platform/index.ts index 65b294d9aca..15e21824c48 100644 --- a/cocos/core/platform/index.ts +++ b/cocos/core/platform/index.ts @@ -47,3 +47,14 @@ export { export { screen } from './screen'; export type { Screen } from './screen'; +export { + googlePlayBilling, + BillingEventType, + ProductType, + BillingResponseCode, + RecurrenceMode, + ConnectionState, + FeatureType, + PurchaseState, + InAppMessageResponseCode, +} from './billing/billing'; diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index 1b1ff633089..bab664bace8 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1476,4 +1476,349 @@ export declare namespace native { */ onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; } | undefined; + + /** + * @en Params containing the response code and the debug message from In-app Billing API response. + * @zh 包含应用内结算 API 响应代码和调试信息的参数 + */ + export interface BillingResult { + /** + * @en Debug message returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的调试消息。 + */ + readonly debugMessage: string; + /** + * @en Code returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的响应代码。 + */ + readonly code: string; + readonly toStr: string; + } + + /** + * @en Represents the offer details to buy an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface OneTimePurchaseOfferDetails { + /** + * @en The price for the payment in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 以微单位返回支付价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en Formatted price for the payment, including its currency sign. + * @zh 支付的格式化价格,包括其货币单位。 + */ + readonly formattedPrice: string; + /** + * @en ISO 4217 currency code for price. + * @zh 价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents additional details of an installment subscription plan. + * @zh 表示分期付款订阅计划的附加详细信息。 + */ + export interface ProductDetailsInstallmentPlanDetails { + /** + * @en Committed payments count after a user signs up for this subscription plan. + * @zh 用户注册此订阅计划后承诺的付款数量。 + */ + readonly installmentPlanCommitmentPaymentsCount: number; + /** + * @en Subsequent committed payments count after this subscription plan renews. + * @zh 此订阅计划续订后的后续承诺付款数量。 + */ + readonly subsequentInstallmentPlanCommitmentPaymentsCount: number; + } + + /** + * @en Represents a pricing phase, describing how a user pays at a point in time. + * @zh 表示定价阶段,描述用户在某个时间点如何付款。 + */ + export interface PricingPhase { + /** + * @en Number of cycles for which the billing period is applied. + * @zh 计费周期适用的周期数。 + */ + readonly billingCycleCount: number; + /** + * @en The price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 微单位付款周期的价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en RecurrenceMode for the pricing phase. + * @zh 定价阶段的RecurrenceMode。 + */ + readonly recurrenceMode: number; + /** + * @en Billing period for which the given price applies, specified in ISO 8601 format. + * @zh 给定价格适用的计费期,以 ISO 8601 格式指定。 + */ + readonly billingPeriod: string; + /** + * @en Formatted price for the payment cycle, including its currency sign. + * @zh 付款周期的格式化价格,包括其货币符号。 + */ + readonly formattedPrice: string; + /** + * @en Returns ISO 4217 currency code for price. + * @zh 返回价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents the available purchase plans to buy a subscription product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface SubscriptionOfferDetails { + /** + * @en The base plan id associated with the subscription product. + * @zh 与订阅产品相关的基本计划 ID。 + */ + readonly basePlanId: string; + /** + * @en The offer id associated with the subscription product. + * @zh 与订阅产品相关的优惠 ID。 + */ + readonly offerId: string; + /** + * @en The offer tags associated with this Subscription Offer. + * @zh 与此订阅优惠相关的优惠标签。 + */ + readonly offerTags: string[]; + /** + * @en The offer token required to pass in launchBillingFlow to purchase the subscription product with these pricing phases. + * @zh 在 launchBillingFlow 中传递以使用这些定价阶段购买订阅产品所需的优惠令牌。 + */ + readonly offerToken: string; + /** + * @en The pricing phases for the subscription product. + * @zh 订阅产品的定价区间。 + */ + readonly pricingPhaseList: PricingPhase[]; + /** + * @en The additional details of an installment plan. + * @zh 分期付款计划的附加详细信息。 + */ + readonly installmentPlanDetails: ProductDetailsInstallmentPlanDetails; + } + /** + * @en Represents the details of a one time or subscription product. + * @zh 代表一次性或订阅产品的详细信息。 + */ + export interface ProductDetails { + /** + * @en Represents the available purchase plans to buy a subscription product. + * @zh 代表一次性购买产品的报价详情。 + */ + readonly hashCode: number; + /** + * @en The description of the product. + * @zh 产品的描述。 + */ + readonly description: string; + /** + * @en The name of the product being sold. + * @zh 所售产品的名称。 + */ + readonly name: string; + /** + * @en The product's Id. + * @zh 产品的 ID。 + */ + readonly productId: string; + /** + * @en The ProductType of the product. + * @zh ProductType产品的。 + */ + readonly productType: string; + /** + * @en The title of the product being sold. + * @zh 所售产品的标题。 + */ + readonly title: string; + + readonly toStr: string; + /** + * @en The offer details of an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + readonly oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails; + /** + * @en A list containing all available offers to purchase a subscription product. + * @zh 返回包含购买订阅产品的所有可用优惠的列表。 + */ + readonly subscriptionOfferDetails: SubscriptionOfferDetails[]; + } + + /** + * @en Account identifiers that were specified when the purchase was made. + * @zh 购买时指定的帐户标识符。 + */ + export interface AccountIdentifiers { + /** + * @en The obfuscated account id specified in setObfuscatedAccountId. + * @zh 在setObfuscatedAccountId中设置的混淆账户id + */ + readonly obfuscatedAccountId: string; + /** + * @en The obfuscated profile id specified in setObfuscatedProfileId. + * @zh 在setObfuscatedProfileId中设置的混淆profile id + */ + readonly obfuscatedProfileId: string; + } + + /** + * @en Represents a pending change/update to the existing purchase. + * @zh 表示对现有购买的待定更改/更新。 + */ + export interface PendingPurchaseUpdate { + /** + * @en A token that uniquely identifies this pending transaction. + * @zh 唯一标识此待处理交易的令牌。 + */ + readonly purchaseToken: string; + /** + * @en The product ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + /** + * @en Represents an in-app billing purchase. + * @zh 代表应用内付费购买。 + */ + export interface Purchase { + /** + * @en One of PurchaseState indicating the state of the purchase. + * @zh PurchaseState表示购买状态的其中一个值。 + */ + readonly purchaseState: number; + /** + * @en The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970). + * @zh 产品购买的时间,以纪元(1970 年 1 月 1 日)以来的毫秒数表示。 + */ + readonly purchaseTime: number; + /** + * @en Indicates whether the purchase has been acknowledged. + * @zh 表示是否已确认购买。 + */ + readonly isAcknowledged: number; + /** + * @en Indicates whether the subscription renews automatically. + * @zh 指示订阅是否自动续订。 + */ + readonly isAutoRenewing: number; + + readonly hashCode: number; + + /** + * @en The quantity of the purchased product. + * @zh 购买产品的数量。 + */ + readonly quantity: number; + /** + * @en The payload specified when the purchase was acknowledged or consumed. + * @zh 确认或消费购买时指定的有效负载。 + */ + readonly deleloperPayload: string; + /** + * @en Returns a unique order identifier for the transaction. + * @zh 交易的唯一订单标识符。 + */ + readonly orderId: string; + /** + * @en Returns a String in JSON format that contains details about the purchase order. + * @zh 包含有关采购订单详细信息的 JSON 格式的字符串。 + */ + readonly originalJson: string; + /** + * @en The application package from which the purchase originated. + * @zh 购买来源的应用程序包。 + */ + readonly packageName: string; + /** + * @en A token that uniquely identifies a purchase for a given item and user pair. + * @zh 唯一标识给定商品和用户对的购买的令牌。 + */ + readonly purchaseToken: string; + /** + * @en String containing the signature of the purchase data that was signed with the private key of the developer. + * @zh 包含使用开发者私钥签名的购买数据签名的字符串。 + */ + readonly signature: string; + + readonly toStr: string; + + /** + * @en Returns account identifiers that were provided when the purchase was made. + * @zh 返回购买时提供的帐户标识符。 + */ + readonly accountIdentifiers: AccountIdentifiers; + /** + * @en The PendingPurchaseUpdate for an uncommitted transaction. + * @zh 返回PendingPurchaseUpdate未提交的事务。 + */ + readonly pendingPurchaseUpdate: PendingPurchaseUpdate; + /** + * @en the product Ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + export interface BillingConfig { + /** + * @en The customer's country code. + * @zh 客户的国家代码。 + */ + readonly countryCode: string; + } + + /** + * @en The details used to report transactions made via alternative billing without user choice to use Google Play billing. + * @zh 用于报告用户未选择使用 Google Play 结算方式而通过替代结算方式进行的交易的详细信息。 + */ + export interface AlternativeBillingOnlyReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @zh 返回一个外部交易令牌,该令牌可用于报告通过替代付款方式进行的交易,而无需用户选择使用 Google Play 付款方式。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en The details used to report transactions made via external offer. + * @zh 用于报告通过外部报价进行的交易的详细信息。 + */ + export interface ExternalOfferReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via external offer. + * @zh 可用于报告通过外部报价进行的交易的外部交易令牌。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en Results related to in-app messaging. + * @zh 与应用程序内消息相关的结果。 + */ + export interface InAppMessageResult { + /** + * @en Response code for the in-app messaging API call. + * @zh 应用内消息传递 API 调用的响应代码。 + */ + readonly responseCode: number; + /** + * @en Token that identifies the purchase to be acknowledged, if any. + * @zh 返回标识需要确认的购买的令牌。 + */ + readonly purchaseToken: string; + } } diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 7449558b3c5..a6b62874eec 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -761,6 +761,12 @@ if(ANDROID) cocos/platform/android/modules/System.cpp cocos/platform/android/modules/System.h ) + cocos_source_files( + cocos/platform/android/modules/google_play/billing/billing.cpp + cocos/platform/android/modules/google_play/billing/billing.h + cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp + cocos/platform/android/modules/google_play/billing/JniBillingHelper.h + ) elseif(OPENHARMONY) # Because there will be a definition of OHOS in harmonyos, it is necessary to define the macro of OPENHARMONY. elseif(OHOS) @@ -1115,6 +1121,7 @@ elseif(ANDROID) ${CWD}/cocos/platform/java/jni/JniImp.h ${CWD}/cocos/platform/java/jni/glue/MessagePipe.cpp ${CWD}/cocos/platform/java/jni/glue/MessagePipe.h + ${CWD}/cocos/platform/java/jni/JniCocosBilling.cpp ) cocos_source_files(MODULE ccfilesystem @@ -2560,6 +2567,7 @@ cocos_source_files( cocos/bindings/manual/jsb_pipeline_manual.h cocos/bindings/manual/jsb_pipeline_manual.cpp cocos/bindings/manual/jsb_adpf.cpp + cocos/bindings/manual/jsb_google_play_billing.cpp ) if(USE_AUDIO) cocos_source_files( diff --git a/native/cocos/bindings/manual/jsb_google_play_billing.cpp b/native/cocos/bindings/manual/jsb_google_play_billing.cpp new file mode 100644 index 00000000000..b84e76259fc --- /dev/null +++ b/native/cocos/bindings/manual/jsb_google_play_billing.cpp @@ -0,0 +1,1523 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ + +#include "application/ApplicationManager.h" +#include "bindings/jswrapper/SeApi.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "bindings/jswrapper/SeApi.h" + #include "bindings/manual/jsb_conversions.h" + #include "bindings/manual/jsb_global.h" + #include "cocos/platform/android/modules/google_play/billing/billing.h" +JSB_REGISTER_OBJECT_TYPE(cc::BillingResult); +JSB_REGISTER_OBJECT_TYPE(cc::OneTimePurchaseOfferDetails); +JSB_REGISTER_OBJECT_TYPE(cc::ProductDetailsInstallmentPlanDetails); +JSB_REGISTER_OBJECT_TYPE(cc::PricingPhase); +JSB_REGISTER_OBJECT_TYPE(cc::SubscriptionOfferDetails); +JSB_REGISTER_OBJECT_TYPE(cc::ProductDetails); +JSB_REGISTER_OBJECT_TYPE(cc::AccountIdentifiers); +JSB_REGISTER_OBJECT_TYPE(cc::PendingPurchaseUpdate); +JSB_REGISTER_OBJECT_TYPE(cc::Purchase); +JSB_REGISTER_OBJECT_TYPE(cc::BillingConfig); +JSB_REGISTER_OBJECT_TYPE(cc::Billing); + +#endif + +#if CC_PLATFORM == CC_PLATFORM_ANDROID +se::Class* __jsb_cc_BillingResult_class = nullptr; +se::Object* __jsb_cc_BillingResult_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingResult) +static bool js_cc_BillingResult_debugMessage_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->debugMessage; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_debugMessage_get) + +static bool js_cc_BillingResult_code_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->code; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_code_get) + +static bool js_cc_BillingResult_toString_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_toString_get) + +static bool js_delete_cc_BillingResult(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingResult) + +bool js_register_cc_BillingResult(se::Object* obj) { + auto* cls = se::Class::create("BillingResult", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("debugMessage", _SE(js_cc_BillingResult_debugMessage_get), nullptr); + cls->defineProperty("code", _SE(js_cc_BillingResult_code_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_BillingResult_toString_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_BillingResult)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_BillingResult_proto = cls->getProto(); + __jsb_cc_BillingResult_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_OneTimePurchaseOfferDetails_class = nullptr; +se::Object* __jsb_cc_OneTimePurchaseOfferDetails_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) + +static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->formattedPrice, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(arg1->formattedPrice, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_formattedPrice_get) + +static bool js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->priceAmountMicros, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get) + +static bool js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->priceCurrencyCode, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(arg1->priceCurrencyCode, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get) + +static bool js_delete_cc_OneTimePurchaseOfferDetails(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) + +bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { + auto* cls = se::Class::create("OneTimePurchaseOfferDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("formattedPrice", _SE(js_cc_OneTimePurchaseOfferDetails_formattedPrice_get), nullptr); + cls->defineProperty("priceAmountMicros", _SE(js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get), nullptr); + cls->defineProperty("priceCurrencyCode", _SE(js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_OneTimePurchaseOfferDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_OneTimePurchaseOfferDetails_proto = cls->getProto(); + __jsb_cc_OneTimePurchaseOfferDetails_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_ProductDetailsInstallmentPlanDetails_class = nullptr; +se::Object* __jsb_cc_ProductDetailsInstallmentPlanDetails_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetailsInstallmentPlanDetails) +static bool js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetailsInstallmentPlanDetails* arg1 = (cc::ProductDetailsInstallmentPlanDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->installmentPlanCommitmentPaymentsCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get) + +static bool js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetailsInstallmentPlanDetails* arg1 = (cc::ProductDetailsInstallmentPlanDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->subsequentInstallmentPlanCommitmentPaymentsCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get) + +static bool js_delete_cc_ProductDetailsInstallmentPlanDetails(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_ProductDetailsInstallmentPlanDetails) + +bool js_register_cc_ProductDetailsInstallmentPlanDetails(se::Object* obj) { + auto* cls = se::Class::create("ProductDetailsInstallmentPlanDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("installmentPlanCommitmentPaymentsCount", _SE(js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get), nullptr); + cls->defineProperty("subsequentInstallmentPlanCommitmentPaymentsCount", _SE(js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_ProductDetailsInstallmentPlanDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_ProductDetailsInstallmentPlanDetails_proto = cls->getProto(); + __jsb_cc_ProductDetailsInstallmentPlanDetails_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_PricingPhase_class = nullptr; +se::Object* __jsb_cc_PricingPhase_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhase) + +static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->billingCycleCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_billingCycleCount_get) + +static bool js_cc_PricingPhase_priceAmountMicros_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + long result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->priceAmountMicros; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_priceAmountMicros_get) + +static bool js_cc_PricingPhase_recurrenceMode_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->recurrenceMode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_recurrenceMode_get) + +static bool js_cc_PricingPhase_billingPeriod_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->billingPeriod; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_billingPeriod_get) + +static bool js_cc_PricingPhase_formattedPrice_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->formattedPrice; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_formattedPrice_get) + +static bool js_cc_PricingPhase_priceCurrencyCode_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->priceCurrencyCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_priceCurrencyCode_get) + +static bool js_delete_cc_PricingPhase(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PricingPhase) + +bool js_register_cc_PricingPhase(se::Object* obj) { + auto* cls = se::Class::create("PricingPhase", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("billingCycleCount", _SE(js_cc_PricingPhase_billingCycleCount_get), nullptr); + cls->defineProperty("priceAmountMicros", _SE(js_cc_PricingPhase_priceAmountMicros_get), nullptr); + cls->defineProperty("recurrenceMode", _SE(js_cc_PricingPhase_recurrenceMode_get), nullptr); + cls->defineProperty("billingPeriod", _SE(js_cc_PricingPhase_billingPeriod_get), nullptr); + cls->defineProperty("formattedPrice", _SE(js_cc_PricingPhase_formattedPrice_get), nullptr); + cls->defineProperty("priceCurrencyCode", _SE(js_cc_PricingPhase_priceCurrencyCode_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_PricingPhase)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_PricingPhase_proto = cls->getProto(); + __jsb_cc_PricingPhase_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_SubscriptionOfferDetails_class = nullptr; +se::Object* __jsb_cc_SubscriptionOfferDetails_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) + +static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->basePlanId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_basePlanId_get) + +static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + cc::ProductDetailsInstallmentPlanDetails* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->installmentPlanDetails.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_installmentPlanDetails_get) + +static bool js_cc_SubscriptionOfferDetails_offerId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerId_get) + +static bool js_cc_SubscriptionOfferDetails_offerTags_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerTags; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerTags_get) + +static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerToken_get) + +static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pricingPhases->pricingPhaseList; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_pricingPhaseList_get) + +static bool js_delete_cc_SubscriptionOfferDetails(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) + +bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { + auto* cls = se::Class::create("SubscriptionOfferDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("basePlanId", _SE(js_cc_SubscriptionOfferDetails_basePlanId_get), nullptr); + cls->defineProperty("installmentPlanDetails", _SE(js_cc_SubscriptionOfferDetails_installmentPlanDetails_get), nullptr); + cls->defineProperty("offerId", _SE(js_cc_SubscriptionOfferDetails_offerId_get), nullptr); + cls->defineProperty("offerTags", _SE(js_cc_SubscriptionOfferDetails_offerTags_get), nullptr); + cls->defineProperty("offerToken", _SE(js_cc_SubscriptionOfferDetails_offerToken_get), nullptr); + cls->defineProperty("pricingPhaseList", _SE(js_cc_SubscriptionOfferDetails_pricingPhaseList_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_SubscriptionOfferDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_SubscriptionOfferDetails_proto = cls->getProto(); + __jsb_cc_SubscriptionOfferDetails_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_ProductDetails_class = nullptr; +se::Object* __jsb_cc_ProductDetails_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetails) + +static bool js_cc_ProductDetails_equals(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + cc::ProductDetails* arg2 = (cc::ProductDetails*)NULL; + bool result; + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= sevalue_to_native(args[0], &arg2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + result = (bool)((cc::ProductDetails const*)arg1)->equals(*arg2); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + return true; +} +SE_BIND_FUNC(js_cc_ProductDetails_equals) + +static bool js_cc_ProductDetails_hashCode_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->hashCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_hashCode_get) + +static bool js_cc_ProductDetails_description_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->description; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_description_get) + +static bool js_cc_ProductDetails_name_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->name; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_name_get) + +static bool js_cc_ProductDetails_oneTimePurchaseOfferDetails_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + cc::OneTimePurchaseOfferDetails* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->oneTimePurchaseOfferDetails.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_oneTimePurchaseOfferDetails_get) + +static bool js_cc_ProductDetails_productId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->productId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_productId_get) + +static bool js_cc_ProductDetails_productType_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->productType; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_productType_get) + +static bool js_cc_ProductDetails_title_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->title; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_title_get) + +static bool js_cc_ProductDetails_toString_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_toString_get) + +static bool js_cc_ProductDetails_subscriptionOfferDetails_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->subscriptionOfferDetails; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_subscriptionOfferDetails_get) + +static bool js_delete_cc_ProductDetails(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_ProductDetails) + +bool js_register_cc_ProductDetails(se::Object* obj) { + auto* cls = se::Class::create("ProductDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("hashCode", _SE(js_cc_ProductDetails_hashCode_get), nullptr); + cls->defineProperty("description", _SE(js_cc_ProductDetails_description_get), nullptr); + cls->defineProperty("name", _SE(js_cc_ProductDetails_name_get), nullptr); + cls->defineProperty("oneTimePurchaseOfferDetails", _SE(js_cc_ProductDetails_oneTimePurchaseOfferDetails_get), nullptr); + cls->defineProperty("productId", _SE(js_cc_ProductDetails_productId_get), nullptr); + cls->defineProperty("productType", _SE(js_cc_ProductDetails_productType_get), nullptr); + cls->defineProperty("title", _SE(js_cc_ProductDetails_title_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_ProductDetails_toString_get), nullptr); + cls->defineProperty("subscriptionOfferDetails", _SE(js_cc_ProductDetails_subscriptionOfferDetails_get), nullptr); + + cls->defineFunction("equals", _SE(js_cc_ProductDetails_equals)); + + cls->defineFinalizeFunction(_SE(js_delete_cc_ProductDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_ProductDetails_proto = cls->getProto(); + __jsb_cc_ProductDetails_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_AccountIdentifiers_class = nullptr; +se::Object* __jsb_cc_AccountIdentifiers_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) + +static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->obfuscatedAccountId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedAccountId_get) + +static bool js_cc_AccountIdentifiers_obfuscatedProfileId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->obfuscatedProfileId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedProfileId_get) + +static bool js_delete_cc_AccountIdentifiers(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) + +bool js_register_cc_AccountIdentifiers(se::Object* obj) { + auto* cls = se::Class::create("AccountIdentifiers", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("obfuscatedAccountId", _SE(js_cc_AccountIdentifiers_obfuscatedAccountId_get), nullptr); + cls->defineProperty("obfuscatedProfileId", _SE(js_cc_AccountIdentifiers_obfuscatedProfileId_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_AccountIdentifiers)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_AccountIdentifiers_proto = cls->getProto(); + __jsb_cc_AccountIdentifiers_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_PendingPurchaseUpdate_class = nullptr; +se::Object* __jsb_cc_PendingPurchaseUpdate_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) + +static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->products; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_products_get) + +static bool js_cc_PendingPurchaseUpdate_purchaseToken_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_purchaseToken_get) + +static bool js_delete_cc_PendingPurchaseUpdate(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) + +bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { + auto* cls = se::Class::create("PendingPurchaseUpdate", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("products", _SE(js_cc_PendingPurchaseUpdate_products_get), nullptr); + cls->defineProperty("purchaseToken", _SE(js_cc_PendingPurchaseUpdate_purchaseToken_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_PendingPurchaseUpdate)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_PendingPurchaseUpdate_proto = cls->getProto(); + __jsb_cc_PendingPurchaseUpdate_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_Purchase_class = nullptr; +se::Object* __jsb_cc_Purchase_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Purchase) + +static bool js_cc_Purchase_equals(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::Purchase* arg2 = 0; + cc::Purchase temp2; + bool result; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + result = (bool)((cc::Purchase const*)arg1)->equals((cc::Purchase const&)*arg2); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_FUNC(js_cc_Purchase_equals) + +static bool js_cc_Purchase_accountIdentifiers_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::AccountIdentifiers* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->accountIdentifiers.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_accountIdentifiers_get) + +static bool js_cc_Purchase_deleloperPayload_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->deleloperPayload; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_deleloperPayload_get) + +static bool js_cc_Purchase_orderId_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->orderId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_orderId_get) + +static bool js_cc_Purchase_originalJson_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->originalJson; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_originalJson_get) + +static bool js_cc_Purchase_packageName_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->packageName; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_packageName_get) + +static bool js_cc_Purchase_pendingPurchaseUpdate_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::PendingPurchaseUpdate* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pendingPurchaseUpdate.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_pendingPurchaseUpdate_get) + +static bool js_cc_Purchase_products_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->products; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_products_get) + +static bool js_cc_Purchase_purchaseState_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseState; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseState_get) + +static bool js_cc_Purchase_purchaseTime_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + long result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = (long)arg1->purchaseTime; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseTime_get) + +static bool js_cc_Purchase_purchaseToken_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseToken_get) + +static bool js_cc_Purchase_quantity_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->quantity; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_quantity_get) + +static bool js_cc_Purchase_signature_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->signature; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_signature_get) + +static bool js_cc_Purchase_hashCode_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->hashCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_hashCode_get) + +static bool js_cc_Purchase_isAcknowledged_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + bool result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->isAcknowledged; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_isAcknowledged_get) + +static bool js_cc_Purchase_isAutoRenewing_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + bool result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->isAutoRenewing; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_isAutoRenewing_get) + +static bool js_cc_Purchase_toString_get(se::State& s) { + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_toString_get) + +static bool js_new_cc_Purchase(se::State& s) // NOLINT(readability-identifier-naming) +{ + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + + cc::Purchase* result; + result = (cc::Purchase*)new cc::Purchase(); + + auto* ptr = JSB_MAKE_PRIVATE_OBJECT_WITH_INSTANCE(result); + s.thisObject()->setPrivateObject(ptr); + return true; +} +SE_BIND_CTOR(js_new_cc_Purchase, __jsb_cc_Purchase_class, js_delete_cc_Purchase) + +static bool js_delete_cc_Purchase(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_Purchase) + +bool js_register_cc_Purchase(se::Object* obj) { + auto* cls = se::Class::create("Purchase", obj, nullptr, _SE(js_new_cc_Purchase)); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("accountIdentifiers", _SE(js_cc_Purchase_accountIdentifiers_get), nullptr); + cls->defineProperty("deleloperPayload", _SE(js_cc_Purchase_deleloperPayload_get), nullptr); + cls->defineProperty("orderId", _SE(js_cc_Purchase_orderId_get), nullptr); + cls->defineProperty("originalJson", _SE(js_cc_Purchase_originalJson_get), nullptr); + cls->defineProperty("packageName", _SE(js_cc_Purchase_packageName_get), nullptr); + cls->defineProperty("pendingPurchaseUpdate", _SE(js_cc_Purchase_pendingPurchaseUpdate_get), nullptr); + cls->defineProperty("products", _SE(js_cc_Purchase_products_get), nullptr); + cls->defineProperty("purchaseState", _SE(js_cc_Purchase_purchaseState_get), nullptr); + cls->defineProperty("purchaseTime", _SE(js_cc_Purchase_purchaseTime_get), nullptr); + cls->defineProperty("purchaseToken", _SE(js_cc_Purchase_purchaseToken_get), nullptr); + cls->defineProperty("quantity", _SE(js_cc_Purchase_quantity_get), nullptr); + cls->defineProperty("signature", _SE(js_cc_Purchase_signature_get), nullptr); + cls->defineProperty("hashCode", _SE(js_cc_Purchase_hashCode_get), nullptr); + cls->defineProperty("isAcknowledged", _SE(js_cc_Purchase_isAcknowledged_get), nullptr); + cls->defineProperty("isAutoRenewing", _SE(js_cc_Purchase_isAutoRenewing_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_Purchase_toString_get), nullptr); + + cls->defineFunction("equals", _SE(js_cc_Purchase_equals)); + + cls->defineFinalizeFunction(_SE(js_delete_cc_Purchase)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_Purchase_proto = cls->getProto(); + __jsb_cc_Purchase_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +static bool js_cc_BillingConfig_countryCode(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingConfig* arg1 = (cc::BillingConfig*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->countryCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_BillingConfig_countryCode) + +static bool js_delete_cc_BillingConfig(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingConfig) + +se::Class* __jsb_cc_BillingConfig_class = nullptr; +se::Object* __jsb_cc_BillingConfig_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingConfig) + +bool js_register_cc_BillingConfig(se::Object* obj) { + auto* cls = se::Class::create("BillingConfig", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + + cls->defineProperty("countryCode", _SE(js_cc_BillingConfig_countryCode), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_BillingConfig)); + + cls->install(); + JSBClassType::registerClass(cls); + + __jsb_cc_BillingConfig_proto = cls->getProto(); + __jsb_cc_BillingConfig_class = cls; + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +se::Class* __jsb_cc_Billing_class = nullptr; +se::Object* __jsb_cc_Billing_proto = nullptr; +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Billing) + +static bool js_cc_Billing_startConnection(se::State& s) { + cc::Billing::getInstance().startConnection(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_startConnection) + +static bool js_cc_Billing_endConnection(se::State& s) { + cc::Billing::getInstance().endConnection(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_endConnection) + +static bool js_cc_Billing_getConnectionState(se::State& s) { + int connectionState = cc::Billing::getInstance().getConnectionState(); + s.rval().setFloat(connectionState); + return true; +} +SE_BIND_FUNC(js_cc_Billing_getConnectionState) + +static bool js_cc_Billing_isReady(se::State& s) { + int isReady = cc::Billing::getInstance().isReady(); + s.rval().setFloat(isReady); + return true; +} +SE_BIND_FUNC(js_cc_Billing_isReady) + + +static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + + std::vector* arg2 = 0; + std::string* arg3 = 0; + std::vector temp2; + std::string temp3; + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + ok &= sevalue_to_native(args[1], &temp3, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg3 = &temp3; + + cc::Billing::getInstance().queryProductDetailsParams((std::vector const&)*arg2, (std::string const&)*arg3); + return true; +} +SE_BIND_FUNC(js_cc_Billing_queryProductDetailsParams) + +static bool js_cc_Billing_launchBillingFlow(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + + std::vector* arg2 = 0; + std::string* arg3 = 0; + std::vector temp2; + std::string temp3; + + if (argc != 2) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + ok &= sevalue_to_native(args[1], &temp3, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg3 = &temp3; + + cc::Billing::getInstance().launchBillingFlow((std::vector const&)*arg2, (std::string const&)*arg3); + + return true; +} +SE_BIND_FUNC(js_cc_Billing_launchBillingFlow) + +static bool js_cc_Billing_consumePurchases(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::vector* arg2 = 0; + std::vector temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + cc::Billing::getInstance().consumePurchases((std::vector const&)*arg2); + + return true; +} +SE_BIND_FUNC(js_cc_Billing_consumePurchases) + +static bool js_cc_Billing_acknowledgePurchase(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::vector* arg1 = 0; + std::vector temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg1 = &temp2; + + cc::Billing::getInstance().acknowledgePurchase((std::vector const&)*arg1); + + return true; +} +SE_BIND_FUNC(js_cc_Billing_acknowledgePurchase) + +static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::string* arg2 = 0; + std::string temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + cc::Billing::getInstance().queryPurchasesAsync((std::string const&)*arg2); + + return true; +} +SE_BIND_FUNC(js_cc_Billing_queryPurchasesAsync) + +static bool js_cc_Billing_getBillingConfigAsync(se::State& s) { + cc::Billing::getInstance().getBillingConfigAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_getBillingConfigAsync) + +static bool js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { + cc::Billing::getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync) + +static bool js_cc_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { + cc::Billing::getInstance().isAlternativeBillingOnlyAvailableAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_isAlternativeBillingOnlyAvailableAsync) + +static bool js_cc_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { + cc::Billing::getInstance().createExternalOfferReportingDetailsAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_createExternalOfferReportingDetailsAsync) + +static bool js_cc_Billing_isExternalOfferAvailableAsync(se::State& s) { + cc::Billing::getInstance().isExternalOfferAvailableAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Billing_isExternalOfferAvailableAsync) + +static bool js_cc_Billing_isFeatureSupported(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::string temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + cc::BillingResult* result = cc::Billing::getInstance().isFeatureSupported(temp2); + + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Billing_isFeatureSupported) + +static bool js_cc_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::Billing::getInstance().showAlternativeBillingOnlyInformationDialog(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Billing_showAlternativeBillingOnlyInformationDialog) + +static bool js_cc_Billing_showExternalOfferInformationDialog(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::Billing::getInstance().showExternalOfferInformationDialog(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Billing_showExternalOfferInformationDialog) + +static bool js_cc_Billing_showInAppMessages(se::State& s) { + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::Billing::getInstance().showInAppMessages(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Billing_showInAppMessages) + +static bool js_delete_cc_Billing(se::State& s) { + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_Billing) + +bool js_register_cc_Billing(se::Object* obj) { + se::Value billingVal{se::Object::createPlainObject()}; + auto* cls = billingVal.toObject(); + + cls->defineFunction("startConnection", _SE(js_cc_Billing_startConnection)); + cls->defineFunction("endConnection", _SE(js_cc_Billing_endConnection)); + cls->defineFunction("getConnectionState", _SE(js_cc_Billing_getConnectionState)); + cls->defineFunction("isReady", _SE(js_cc_Billing_isReady)); + cls->defineFunction("queryProductDetailsParams", _SE(js_cc_Billing_queryProductDetailsParams)); + + cls->defineFunction("launchBillingFlow", _SE(js_cc_Billing_launchBillingFlow)); + cls->defineFunction("consumePurchases", _SE(js_cc_Billing_consumePurchases)); + cls->defineFunction("acknowledgePurchase", _SE(js_cc_Billing_acknowledgePurchase)); + cls->defineFunction("queryPurchasesAsync", _SE(js_cc_Billing_queryPurchasesAsync)); + cls->defineFunction("getBillingConfigAsync", _SE(js_cc_Billing_getBillingConfigAsync)); + + cls->defineFunction("createAlternativeBillingOnlyReportingDetailsAsync", _SE(js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync)); + cls->defineFunction("isAlternativeBillingOnlyAvailableAsync", _SE(js_cc_Billing_isAlternativeBillingOnlyAvailableAsync)); + cls->defineFunction("createExternalOfferReportingDetailsAsync", _SE(js_cc_Billing_createExternalOfferReportingDetailsAsync)); + cls->defineFunction("isExternalOfferAvailableAsync", _SE(js_cc_Billing_isExternalOfferAvailableAsync)); + + cls->defineFunction("isFeatureSupported", _SE(js_cc_Billing_isFeatureSupported)); + + cls->defineFunction("showAlternativeBillingOnlyInformationDialog", _SE(js_cc_Billing_showAlternativeBillingOnlyInformationDialog)); + cls->defineFunction("showExternalOfferInformationDialog", _SE(js_cc_Billing_showExternalOfferInformationDialog)); + cls->defineFunction("showInAppMessages", _SE(js_cc_Billing_showInAppMessages)); + + obj->setProperty("billing", billingVal); + + return true; +} + +bool jsb_register_google_play_billing(se::Object* obj) { + se::Value nsVal; + if (!obj->getProperty("jsb", &nsVal, true)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("jsb", nsVal); + } + se::Object* ns = nsVal.toObject(); + /* Register classes */ + js_register_cc_BillingResult(ns); + js_register_cc_OneTimePurchaseOfferDetails(ns); + js_register_cc_ProductDetailsInstallmentPlanDetails(ns); + js_register_cc_PricingPhase(ns); + + js_register_cc_SubscriptionOfferDetails(ns); + js_register_cc_ProductDetails(ns); + js_register_cc_AccountIdentifiers(ns); + js_register_cc_PendingPurchaseUpdate(ns); + js_register_cc_Purchase(ns); + js_register_cc_BillingConfig(ns); + js_register_cc_Billing(ns); + return true; +} + +#else +void jsb_register_google_play_billing(se::Object *ns) {} // NOLINT + +#endif // CC_PLATFORM_ANDROID diff --git a/native/cocos/bindings/manual/jsb_module_register.cpp b/native/cocos/bindings/manual/jsb_module_register.cpp index 63237ec6cef..94f92b6e65b 100644 --- a/native/cocos/bindings/manual/jsb_module_register.cpp +++ b/native/cocos/bindings/manual/jsb_module_register.cpp @@ -125,6 +125,8 @@ #include "cocos/bindings/auto/jsb_physics_auto.h" #endif +extern bool jsb_register_google_play_billing(se::Object *); // NOLINT + bool jsb_register_all_modules() { se::ScriptEngine *se = se::ScriptEngine::getInstance(); @@ -190,6 +192,10 @@ bool jsb_register_all_modules() { se->addRegisterCallback(register_all_socketio); #endif +#if CC_PLATFORM == CC_PLATFORM_ANDROID + se->addRegisterCallback(jsb_register_google_play_billing); +#endif + #if CC_USE_MIDDLEWARE se->addRegisterCallback(register_all_editor_support); diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java new file mode 100644 index 00000000000..3033144cc9f --- /dev/null +++ b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java @@ -0,0 +1,471 @@ +/**************************************************************************** +Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.InAppMessageResult; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.AcknowledgePurchaseParams; +import com.android.billingclient.api.AcknowledgePurchaseResponseListener; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.ConsumeParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetailsListener; +import com.android.billingclient.api.ProductDetailsResponseListener; +import com.android.billingclient.api.PurchasesResponseListener; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryPurchasesParams; +import com.android.billingclient.api.GetBillingConfigParams; +import com.android.billingclient.api.BillingConfigResponseListener; +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; +import com.android.billingclient.api.AlternativeBillingOnlyAvailabilityListener; +import com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener; +import com.android.billingclient.api.ExternalOfferReportingDetailsListener; +import com.android.billingclient.api.ExternalOfferReportingDetails; +import com.android.billingclient.api.ExternalOfferAvailabilityListener; +import com.android.billingclient.api.ExternalOfferInformationDialogListener; +import com.android.billingclient.api.InAppMessageParams; +import com.android.billingclient.api.InAppMessageResponseListener; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CocosBilling implements BillingClientStateListener, + PurchasesUpdatedListener, ProductDetailsResponseListener, PurchasesResponseListener, + ConsumeResponseListener, AcknowledgePurchaseResponseListener, BillingConfigResponseListener, + AlternativeBillingOnlyReportingDetailsListener, AlternativeBillingOnlyAvailabilityListener,AlternativeBillingOnlyInformationDialogListener, + ExternalOfferReportingDetailsListener, ExternalOfferAvailabilityListener,ExternalOfferInformationDialogListener, InAppMessageResponseListener { + + private static final String TAG = CocosBilling.class.getSimpleName(); + private Map _productDetails = new HashMap<>(); + private Map _purchase = new HashMap<>(); + + /** + * The billing client. + */ + private BillingClient _billingClient; + + public CocosBilling() { + _billingClient = BillingClient.newBuilder(GlobalObject.getActivity()) + .setListener(this) + .enablePendingPurchases() + .build(); + } + + public void startConnection() { + _billingClient.startConnection(this); + } + public void endConnection() { + _billingClient.endConnection(); + } + + public int getConnectionState() { + return _billingClient.getConnectionState(); + } + + public BillingResult isFeatureSupported(String feature) { + return _billingClient.isFeatureSupported(feature); + } + + public boolean isReady() { + return _billingClient.isReady(); + } + + public boolean isConnected() { + return getConnectionState() == BillingClient.ConnectionState.CONNECTED; + } + + public void createAlternativeBillingOnlyReportingDetailsAsync() { + _billingClient.createAlternativeBillingOnlyReportingDetailsAsync(this); + } + + public void isAlternativeBillingOnlyAvailableAsync() { + _billingClient.isAlternativeBillingOnlyAvailableAsync(this); + } + + public void createExternalOfferReportingDetailsAsync() { + _billingClient.createExternalOfferReportingDetailsAsync(this); + } + + public void isExternalOfferAvailableAsync() { + _billingClient.isExternalOfferAvailableAsync(this); + } + + public void queryProductDetailsParams(String[] productIds, String type) { + if(!isConnected()) { + Log.e(TAG, "Must be connected before use this interface"); + return; + } + if(productIds.length == 0) { + Log.e(TAG, "Product ID cannot be empty"); + return; + } + String inputType; + if(type.equals(BillingClient.ProductType.INAPP)) { + inputType = BillingClient.ProductType.INAPP; + } else if(type.equals(BillingClient.ProductType.SUBS)) { + inputType = BillingClient.ProductType.SUBS; + } else { + Log.e(TAG, "Undefined product types."); + return; + } + List products = new ArrayList<>(); + for(String productId: productIds) { + products.add( + QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(inputType) + .build()); + } + QueryProductDetailsParams params = + QueryProductDetailsParams.newBuilder().setProductList(products).build(); + _billingClient.queryProductDetailsAsync(params, this); + } + + public void launchBillingFlow(int[] productDetailsHashs, String selectedOfferToken) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + List productDetailsParamsList = new ArrayList<>(); + for (int productDetailsHash: productDetailsHashs) { + if(_productDetails.containsKey(productDetailsHash)) { + if(selectedOfferToken.isEmpty()) { + productDetailsParamsList.add( + BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(_productDetails.get(productDetailsHash)).build() + ); + } else { + productDetailsParamsList.add( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(_productDetails.get(productDetailsHash)) + .setOfferToken(selectedOfferToken) + .build() + ); + } + } else { + Log.w(TAG, "Purchased product ID does not exist"); + } + + } + BillingFlowParams params = BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParamsList).build(); + _billingClient.launchBillingFlow(GlobalObject.getActivity(), params); + } + + public void queryPurchasesAsync(String type) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + String inputType; + if(type.equals(BillingClient.ProductType.INAPP)) { + inputType = BillingClient.ProductType.INAPP; + } else if(type.equals(BillingClient.ProductType.SUBS)) { + inputType = BillingClient.ProductType.SUBS; + } else { + Log.w(TAG, "Undefined product types."); + return; + } + _billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder() + .setProductType(inputType) + .build(), this); + } + + public void consumePurchase(@Nullable Purchase purchase) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + if(purchase != null && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + _billingClient.consumeAsync(ConsumeParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()).build(), + this); + } + } + + private void consumePurchases(@NonNull List purchases) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + for (Purchase purchase : purchases) { + consumePurchase(purchase); + } + } + + public void consumePurchases(@NonNull int[] purchaseHashs) { + if(purchaseHashs.length == 0) { + return; + } + List purchases = new ArrayList<>(); + for (int purchaseHash: purchaseHashs) { + if(_purchase.containsKey(purchaseHash)) { + purchases.add(_purchase.get(purchaseHash)); + } else { + Log.w(TAG, "Purchase id for consumption does not exist"); + } + } + if (!isConnected()) { + startConnection(); + return; + } + consumePurchases(purchases); + } + + private void acknowledgePurchase(@Nullable Purchase purchase) { + if (!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + + if (purchase != null && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + //if (!purchase.isAcknowledged()) { + _billingClient.acknowledgePurchase(AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()).build(), + this); + //} + } + } + + private void acknowledgePurchases(@Nullable List purchases) { + if (!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + if(purchases != null) { + for (Purchase purchase : purchases) { + acknowledgePurchase(purchase); + } + } + } + + public void acknowledgePurchases(@NonNull int[] purchaseHashs) { + List purchases = new ArrayList<>(); + for (int purchaseHash: purchaseHashs) { + if(_purchase.containsKey(purchaseHash)) { + purchases.add(_purchase.get(purchaseHash)); + } else { + Log.w(TAG, "Purchase id for acknowledge does not exist"); + } + } + acknowledgePurchases(purchases); + } + + public void getBillingConfigAsync() { + GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build(); + _billingClient.getBillingConfigAsync(getBillingConfigParams, this); + } + + public BillingResult showAlternativeBillingOnlyInformationDialog() { + return _billingClient.showAlternativeBillingOnlyInformationDialog(GlobalObject.getActivity(), this); + } + + public BillingResult showExternalOfferInformationDialog() { + return _billingClient.showExternalOfferInformationDialog(GlobalObject.getActivity(), this); + } + public BillingResult showInAppMessages() { + return _billingClient.showInAppMessages(GlobalObject.getActivity(), InAppMessageParams.newBuilder().build(), this); + } + + @Override + public void onBillingSetupFinished(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onBillingSetupFinished(billingResult); + } + }); + } + + public void onBillingServiceDisconnected() { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onBillingServiceDisconnected(); + } + }); + } + + @Override + public void onProductDetailsResponse(@NonNull BillingResult billingResult, + @NonNull List productDetailsList) { + for (ProductDetails productDetails: productDetailsList) { + _productDetails.put(productDetails.hashCode(), productDetails); + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onProductDetailsResponse(billingResult, productDetailsList); + } + }); + } + + @Override + public void onPurchasesUpdated(@NonNull BillingResult billingResult, + @Nullable List purchaseList) { + if(purchaseList != null) { + for (Purchase purchase: purchaseList) { + _purchase.put(purchase.hashCode(), purchase); + } + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onPurchasesUpdated(billingResult, purchaseList); + } + }); + } + + @Override + public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, + @NonNull List purchaseList) { + for (Purchase purchase: purchaseList) { + _purchase.put(purchase.hashCode(), purchase); + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList); + } + }); + } + + @Override + public void onConsumeResponse(@NonNull BillingResult billingResult, + @NonNull String purchaseToken) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onConsumeResponse(billingResult, purchaseToken); + } + }); + } + + @Override + public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onAcknowledgePurchaseResponse(billingResult); + } + }); + } + + @Override + public void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nullable BillingConfig billingConfig) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onBillingConfigResponse(billingResult, billingConfig); + } + }); + } + + @Override + public void onAlternativeBillingOnlyTokenResponse( + @NonNull BillingResult billingResult, + @Nullable AlternativeBillingOnlyReportingDetails alternativeBillingOnlyReportingDetails) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onAlternativeBillingOnlyTokenResponse(billingResult, alternativeBillingOnlyReportingDetails); + } + }); + } + + @Override + public void onAlternativeBillingOnlyAvailabilityResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onAlternativeBillingOnlyAvailabilityResponse(billingResult); + } + }); + } + + @Override + public void onExternalOfferReportingDetailsResponse( + @NonNull BillingResult billingResult, + @Nullable ExternalOfferReportingDetails externalOfferReportingDetails + ) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onExternalOfferReportingDetailsResponse(billingResult, externalOfferReportingDetails); + } + }); + } + + @Override + public void onExternalOfferAvailabilityResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onExternalOfferAvailabilityResponse(billingResult); + } + }); + } + + @Override + public void onExternalOfferInformationDialogResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onExternalOfferInformationDialogResponse(billingResult); + } + }); + } + + @Override + public void onAlternativeBillingOnlyInformationDialogResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onAlternativeBillingOnlyInformationDialogResponse(billingResult); + } + }); + } + + @Override + public void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosBillingHelper.onInAppMessageResponse(inAppMessageResult); + } + }); + } +} diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java new file mode 100644 index 00000000000..1ba310dd4bb --- /dev/null +++ b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java @@ -0,0 +1,207 @@ +/**************************************************************************** +Copyright (c) 2024 + Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; +import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.ExternalOfferReportingDetails; +import com.android.billingclient.api.InAppMessageResult; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.Purchase; +import java.util.List; + +import com.cocos.lib.CocosBilling; + +public class CocosBillingHelper { + public static native void onBillingSetupFinished(@NonNull BillingResult billingResult); + public static native void onBillingServiceDisconnected(); + public static native void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList); + public static native void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchasesList); + public static native void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List purchasesList); + public static native void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String purchaseToken); + public static native void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult); + public static native void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nullable BillingConfig billingConfig); + + public static native void onAlternativeBillingOnlyTokenResponse(@NonNull BillingResult billingResult, + @Nullable AlternativeBillingOnlyReportingDetails alternativeBillingOnlyReportingDetails); + public static native void onAlternativeBillingOnlyAvailabilityResponse(@NonNull BillingResult billingResult); + + public static native void onExternalOfferReportingDetailsResponse( + @NonNull BillingResult billingResult, + @Nullable ExternalOfferReportingDetails externalOfferReportingDetails + ); + public static native void onExternalOfferAvailabilityResponse(@NonNull BillingResult billingResult); + public static native void onExternalOfferInformationDialogResponse(@NonNull BillingResult billingResult); + public static native void onAlternativeBillingOnlyInformationDialogResponse(@NonNull BillingResult billingResult); + public static native void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResult); + + @SuppressLint("StaticFieldLeak") + private static CocosBilling instance; + + public static CocosBilling getInstance() { + if (instance == null) { + instance = new CocosBilling(); + } + return instance; + } + + public static void startConnection() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().startConnection(); + } + }); + } + + public static void endConnection() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().endConnection(); + } + }); + } + + public static int getConnectionState() { + return getInstance().getConnectionState(); + } + + public static BillingResult isFeatureSupported(String feature) { + return getInstance().isFeatureSupported(feature); + } + + public static boolean isReady() { + return getInstance().isReady(); + } + + public static void createAlternativeBillingOnlyReportingDetailsAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); + } + }); + } + + public static void isAlternativeBillingOnlyAvailableAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().isAlternativeBillingOnlyAvailableAsync(); + } + }); + } + + public static void createExternalOfferReportingDetailsAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().createExternalOfferReportingDetailsAsync(); + } + }); + } + + public static void isExternalOfferAvailableAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().isExternalOfferAvailableAsync(); + } + }); + } + + public static void queryProductDetailsParams(String[] productIds, String type) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().queryProductDetailsParams(productIds, type); + } + }); + } + + public static void launchBillingFlow(int[] productDetailsHashs, String selectedOfferToken) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().launchBillingFlow(productDetailsHashs, selectedOfferToken); + } + }); + } + + public static void queryPurchasesAsync(String type) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().queryPurchasesAsync(type); + } + }); + } + + public static void consumePurchases(@NonNull int[] purchaseHashs) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().consumePurchases(purchaseHashs); + } + }); + } + + public static void acknowledgePurchases(@NonNull int[] purchaseHashs) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().acknowledgePurchases(purchaseHashs); + } + }); + } + + public static void getBillingConfigAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().getBillingConfigAsync(); + } + }); + } + + public static BillingResult showAlternativeBillingOnlyInformationDialog() { + return getInstance().showAlternativeBillingOnlyInformationDialog(); + } + + public static BillingResult showExternalOfferInformationDialog() { + return getInstance().showExternalOfferInformationDialog(); + } + public static BillingResult showInAppMessages() { + return getInstance().showInAppMessages(); + } +} diff --git a/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml b/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml index b6ea80dffee..5364438c306 100644 --- a/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml +++ b/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml @@ -1,4 +1,5 @@ + diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 5c47318e45b..540aa97b656 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -6,8 +6,8 @@ android { defaultConfig { minSdkVersion PROP_MIN_SDK_VERSION targetSdkVersion PROP_TARGET_SDK_VERSION - versionCode 1 - versionName "1.0" + versionCode 7 + versionName "7.0" consumerProguardFiles 'proguard-rules.pro' } @@ -37,4 +37,7 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') + api "com.pranavpandey.android:dynamic-utils:4.6.1" + api "com.android.billingclient:billing:7.0.0" + implementation "com.pranavpandey.android:dynamic-support:6.4.0" } diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp new file mode 100644 index 00000000000..93185af8a25 --- /dev/null +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp @@ -0,0 +1,583 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "platform/android/modules/google_play/billing/JniBillingHelper.h" +#include "platform/android/modules/google_play/billing/billing.h" +#include "platform/java/jni/JniHelper.h" + +#include +#include "base/UTF8.h" +#include "base/memory/Memory.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/jswrapper/Value.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" + +namespace { + +#ifndef JCLS_BILLING + #define JCLS_BILLING "com/cocos/lib/CocosBillingHelper" +#endif +}; // namespace + +namespace cc { +namespace { +std::string callStringMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()Ljava/lang/String;"); + jobject jStringObj = env->CallObjectMethod(obj, methodId); + if (jStringObj != nullptr) { + return cc::StringUtils::getStringUTFCharsJNI(env, static_cast(jStringObj)); + } + return ""; +} +jobject callObjectMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName, const char* returnType) { + std::string returnSign = cc::StringUtils::format("()L%s", returnType); + jmethodID methodId = env->GetMethodID(clazz, methodName, returnSign.c_str()); + return env->CallObjectMethod(obj, methodId); +} + +int callIntMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()I"); + return env->CallIntMethod(obj, methodId); +} + +bool callBooleanMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()Z"); + return env->CallBooleanMethod(obj, methodId); +} + +long callLongMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()J"); + return env->CallLongMethod(obj, methodId); +} +} // namespace + +template +void callJSfunc(const char* jsFunctionName, Args&&... inargs) { // NOLINT(readability-identifier-naming) + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + se::AutoHandleScope scope; + se::ValueArray args; + args.resize(sizeof...(Args)); + nativevalue_to_se_args_v(args, inargs...); + se::Value func; + __jsbObj->getProperty(jsFunctionName, &func); + if (func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(args, nullptr); + } + return; +} + +void callJSfunc(const char* jsFunctionName) { // NOLINT(readability-identifier-naming) + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + se::AutoHandleScope scope; + se::ValueArray args; + se::Value func; + __jsbObj->getProperty(jsFunctionName, &func); + if (func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(args, nullptr); + } +} + +template void callJSfunc(const char*, BillingResult*&&); +template void callJSfunc(const char*, BillingResult*&&, const std::vector&); +template void callJSfunc(const char*, BillingResult*&&, const std::vector&); +template void callJSfunc(const char*, BillingResult*&&, const std::string&); +template void callJSfunc(const char*, BillingResult*&&, BillingConfig*&&); +template void callJSfunc(const char*, BillingResult*&&, AlternativeBillingOnlyReportingDetails*&&); +template void callJSfunc(const char*, BillingResult*&&, ExternalOfferReportingDetails*&&); +template void callJSfunc(const char*, InAppMessageResult*&&); + +void JniBillingHelper::startConnection() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "startConnection"); +} + +void JniBillingHelper::endConnection() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "endConnection"); +} + +int JniBillingHelper::getConnectionState() { + return JniHelper::callStaticIntMethod(JCLS_BILLING, "getConnectionState"); +} + +bool JniBillingHelper::isReady() { + return JniHelper::callStaticBooleanMethod(JCLS_BILLING, "isReady"); +} + +void JniBillingHelper::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryProductDetailsParams", productIds, type); +} + +void JniBillingHelper::queryPurchasesAsync(const std::string& productType) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryPurchasesAsync", productType); +} + +void JniBillingHelper::getBillingConfigAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "getBillingConfigAsync"); +} + +void JniBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "createAlternativeBillingOnlyReportingDetailsAsync"); +} + +void JniBillingHelper::isAlternativeBillingOnlyAvailableAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "isAlternativeBillingOnlyAvailableAsync"); +} + +void JniBillingHelper::createExternalOfferReportingDetailsAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "createExternalOfferReportingDetailsAsync"); +} + +void JniBillingHelper::isExternalOfferAvailableAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "isExternalOfferAvailableAsync"); +} + +BillingResult* JniBillingHelper::showAlternativeBillingOnlyInformationDialog() { + return callFunctionAndReturnBillingResult("showAlternativeBillingOnlyInformationDialog"); +} + +BillingResult* JniBillingHelper::showExternalOfferInformationDialog() { + return callFunctionAndReturnBillingResult("showExternalOfferInformationDialog"); +} + +BillingResult* JniBillingHelper::showInAppMessages() { + return callFunctionAndReturnBillingResult("showInAppMessages"); +} + +void JniBillingHelper::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { + if (productDetailsList.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = productDetailsList.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = productDetailsList[i]->hashCode; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "launchBillingFlow", "([ILjava/lang/String;)V"); + jstring offerToken = env->NewStringUTF(selectedOfferToken.c_str()); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result, offerToken); +} + +void JniBillingHelper::consumePurchases(const std::vector& purchases) { + if (purchases.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = purchases.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = purchases[i]->hashCode; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "consumePurchases", "([I)V"); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result); +} + +void JniBillingHelper::acknowledgePurchase(const std::vector& purchases) { + if (purchases.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = purchases.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = purchases[i]->hashCode; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "acknowledgePurchases", "([I)V"); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result); +} + +BillingResult* JniBillingHelper::callFunctionAndReturnBillingResult(const std::string& functionName) { + auto* env = JniHelper::getEnv(); + cc::JniMethodInfo t; + if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, functionName.c_str(), "()Lcom/android/billingclient/api/BillingResult;")) { + jobject obj = t.env->CallStaticObjectMethod(t.classID, t.methodID); + return toBillingResult(env, obj); + } + return nullptr; +} + +BillingResult* JniBillingHelper::isFeatureSupported(const std::string& feature) { + auto* env = JniHelper::getEnv(); + cc::JniMethodInfo t; + if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "isFeatureSupported", "(Ljava/lang/String;)Lcom/android/billingclient/api/BillingResult;")) { + jstring jFeature = cc::StringUtils::newStringUTFJNI(env, feature); + jobject obj = t.env->CallStaticObjectMethod(t.classID, t.methodID, jFeature); + return toBillingResult(env, obj); + } + return nullptr; +} + +void JniBillingHelper::onBillingSetupFinished(JNIEnv* env, jclass clazz, jobject billingResultObj) { + cc::callJSfunc("onBillingSetupFinished", toBillingResult(env, billingResultObj)); +} + +void JniBillingHelper::onBillingServiceDisconnected(JNIEnv* env, jclass clazz) { + cc::callJSfunc("onBillingServiceDisconnected"); +} + +void JniBillingHelper::onProductDetailsResponse(JNIEnv* env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + std::vector productDetailsList = cc::JniBillingHelper::toProductDetailList(env, productDetailsListObj); + cc::callJSfunc("onProductDetailsResponse", billingResult, productDetailsList); +} + +void JniBillingHelper::onPurchasesUpdated(JNIEnv* env, jclass clazz, + jobject billingResultObj, + jobject purchasesListObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + if (purchasesListObj != nullptr) { + std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj); + cc::callJSfunc("onPurchasesUpdated", billingResult, purchasesList); + } else { + cc::callJSfunc("onPurchasesUpdated", billingResult, std::vector()); + } +} + +void JniBillingHelper::onConsumeResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onConsumeResponse", billingResult, cc::StringUtils::getStringUTFCharsJNI(env, static_cast(purchaseToken))); +} + +void JniBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesObj); + cc::callJSfunc("onQueryPurchasesResponse", billingResult, purchasesList); +} + +void JniBillingHelper::onAcknowledgePurchaseResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAcknowledgePurchaseResponse", billingResult); +} + +void JniBillingHelper::onBillingConfigResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::BillingConfig* billingConfig = nullptr; + if (billingConfigObj) { + billingConfig = cc::JniBillingHelper::toBillingConfig(env, billingConfigObj); + } + cc::callJSfunc("onBillingConfigResponse", billingResult, billingConfig); +} +void JniBillingHelper::onAlternativeBillingOnlyTokenResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::AlternativeBillingOnlyReportingDetails* toAlternativeBillingOnlyReporting = nullptr; + if (alternativeBillingOnlyReportingDetailsObj) { + toAlternativeBillingOnlyReporting = cc::JniBillingHelper::toAlternativeBillingOnlyReportingDetails(env, alternativeBillingOnlyReportingDetailsObj); + } + cc::callJSfunc("onAlternativeBillingOnlyTokenResponse", billingResult, toAlternativeBillingOnlyReporting); +} + +void JniBillingHelper::onExternalOfferReportingDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::ExternalOfferReportingDetails* externalOfferReportingDetails = nullptr; + if (externalOfferReportingDetailsObj) { + externalOfferReportingDetails = cc::JniBillingHelper::toExternalOfferReportingDetails(env, externalOfferReportingDetailsObj); + } + cc::callJSfunc("onExternalOfferReportingDetailsResponse", billingResult, externalOfferReportingDetails); +} + +void JniBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAlternativeBillingOnlyAvailabilityResponse", billingResult); +} + +void JniBillingHelper::onExternalOfferAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onExternalOfferAvailabilityResponse", billingResult); +} + +void JniBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAlternativeBillingOnlyInformationDialogResponse", billingResult); +} + +void JniBillingHelper::onExternalOfferInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onExternalOfferInformationDialogResponse", billingResult); +} + +void JniBillingHelper::onInAppMessageResponse(JNIEnv* env, jclass clazz, jobject inAppMessageResultObj) { + auto* inAppMessageResult = cc::JniBillingHelper::toInAppMessageResult(env, inAppMessageResultObj); + cc::callJSfunc("onInAppMessageResponse", inAppMessageResult); +} + +cc::BillingResult* JniBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* billingResult = ccnew cc::BillingResult; + billingResult->debugMessage = callStringMethod(env, clazz, obj, "getDebugMessage"); + billingResult->code = callIntMethod(env, clazz, obj, "getResponseCode"); + billingResult->toString = callStringMethod(env, clazz, obj, "toString"); + return billingResult; +} + +cc::OneTimePurchaseOfferDetails* JniBillingHelper::toOneTimePurchaseOfferDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto oneTimePurchaseOfferDetails = new cc::OneTimePurchaseOfferDetails; + oneTimePurchaseOfferDetails->formattedPrice = callStringMethod(env, clazz, obj, "getFormattedPrice"); + oneTimePurchaseOfferDetails->priceAmountMicros = callLongMethod(env, clazz, obj, "getPriceAmountMicros"); + oneTimePurchaseOfferDetails->priceCurrencyCode = callStringMethod(env, clazz, obj, "getPriceCurrencyCode"); + return oneTimePurchaseOfferDetails; +} + +cc::ProductDetailsInstallmentPlanDetails* JniBillingHelper::toProductDetailsInstallmentPlanDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* installmentPlanDetails = new cc::ProductDetailsInstallmentPlanDetails; + installmentPlanDetails->installmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "getInstallmentPlanCommitmentPaymentsCount"); + installmentPlanDetails->subsequentInstallmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "subsequentInstallmentPlanCommitmentPaymentsCount"); + return installmentPlanDetails; +} + +cc::PricingPhase* JniBillingHelper::toPricingPhase(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + + auto* pricingPhase = new cc::PricingPhase; + pricingPhase->billingCycleCount = callIntMethod(env, clazz, obj, "getBillingCycleCount"); + pricingPhase->billingPeriod = callStringMethod(env, clazz, obj, "getBillingPeriod"); + pricingPhase->formattedPrice = callStringMethod(env, clazz, obj, "getFormattedPrice"); + pricingPhase->priceAmountMicros = callLongMethod(env, clazz, obj, "getPriceAmountMicros"); + pricingPhase->priceCurrencyCode = callStringMethod(env, clazz, obj, "getPriceCurrencyCode"); + pricingPhase->recurrenceMode = callIntMethod(env, clazz, obj, "getRecurrenceMode"); + return pricingPhase; +} + +cc::PricingPhases* JniBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + + auto* pricingPhases = new cc::PricingPhases; + jmethodID methodId = env->GetMethodID(clazz, "getPricingPhaseList", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID alistGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject pricingPhaseObj = env->CallObjectMethod(listObj, alistGetMethod, i); + cc::PricingPhase* pricingPhase = toPricingPhase(env, pricingPhaseObj); + pricingPhases->pricingPhaseList.push_back(pricingPhase); + } + return pricingPhases; +} + +cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* details = ccnew cc::SubscriptionOfferDetails; + details->basePlanId = callStringMethod(env, clazz, obj, + "getBasePlanId"); + + jobject productDetailsInstallmentPlanDetailsObj = callObjectMethod(env, clazz, obj, "getInstallmentPlanDetails", "com/android/billingclient/api/ProductDetails$InstallmentPlanDetails;"); + if (productDetailsInstallmentPlanDetailsObj != nullptr) { + details->installmentPlanDetails.reset(toProductDetailsInstallmentPlanDetails(env, productDetailsInstallmentPlanDetailsObj)); + } + details->offerId = callStringMethod(env, clazz, obj, "getOfferId"); + + jmethodID methodId = env->GetMethodID(clazz, "getOfferTags", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + details->offerTags.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + details->offerToken = callStringMethod(env, clazz, obj, "getOfferToken"); + jobject pricingPhasesObj = callObjectMethod(env, clazz, obj, "getPricingPhases", "com/android/billingclient/api/ProductDetails$PricingPhases;"); + details->pricingPhases.reset(toPricingPhases(env, pricingPhasesObj)); + + return details; +} + +std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj) { + jclass clazz = env->GetObjectClass(productListObj); + jmethodID alistGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, clazz, productListObj, "size"); + std::vector productDetailsList; + for (int i = 0; i < size; ++i) { + jobject productDetailObj = env->CallObjectMethod(productListObj, alistGetMethod, i); + cc::ProductDetails* productDetails = cc::JniBillingHelper::toProductDetail(env, productDetailObj); + productDetailsList.push_back(productDetails); + } + return std::move(productDetailsList); +} + +cc::ProductDetails* JniBillingHelper::toProductDetail(JNIEnv* env, jobject productObj) { + jclass clazz = env->GetObjectClass(productObj); + auto* productDetails = new cc::ProductDetails; + + productDetails->hashCode = callIntMethod(env, clazz, productObj, "hashCode"); + productDetails->description = callStringMethod(env, clazz, productObj, "getDescription"); + productDetails->name = callStringMethod(env, clazz, productObj, "getName"); + productDetails->productId = callStringMethod(env, clazz, productObj, "getProductId"); + productDetails->productType = callStringMethod(env, clazz, productObj, "getProductType"); + productDetails->title = callStringMethod(env, clazz, productObj, "getTitle"); + productDetails->toString = callStringMethod(env, clazz, productObj, "toString"); + + jmethodID methodId = env->GetMethodID(clazz, "getOneTimePurchaseOfferDetails", "()Lcom/android/billingclient/api/ProductDetails$OneTimePurchaseOfferDetails;"); + jobject oneTimePurchaseOfferDetailsObj = env->CallObjectMethod(productObj, methodId); + if (oneTimePurchaseOfferDetailsObj != nullptr) { + productDetails->oneTimePurchaseOfferDetails.reset(toOneTimePurchaseOfferDetails(env, oneTimePurchaseOfferDetailsObj)); + } + + jmethodID getSubscriptionOfferDetails = env->GetMethodID(clazz, "getSubscriptionOfferDetails", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(productObj, getSubscriptionOfferDetails); + if (listObj != nullptr) { + jclass listClazz = env->GetObjectClass(listObj); + jmethodID alistGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + std::vector details; + for (int i = 0; i < size; ++i) { + jobject subscriptionOfferDetailsObj = env->CallObjectMethod(listObj, alistGetMethod, i); + cc::SubscriptionOfferDetails* detail = toSubscriptionOfferDetails(env, subscriptionOfferDetailsObj); + productDetails->subscriptionOfferDetails.push_back(detail); + } + } + + return productDetails; +} + +cc::AccountIdentifiers* JniBillingHelper::toAccountIdentifiers(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* accountIdentifiers = new cc::AccountIdentifiers; + accountIdentifiers->obfuscatedAccountId = callStringMethod(env, clazz, obj, "getObfuscatedAccountId"); + accountIdentifiers->obfuscatedProfileId = callStringMethod(env, clazz, obj, "getObfuscatedProfileId"); + return accountIdentifiers; +} + +cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* pendingPurchaseUpdate = new cc::PendingPurchaseUpdate; + jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/ArrayList;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/String;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + pendingPurchaseUpdate->products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + pendingPurchaseUpdate->purchaseToken = callStringMethod(env, clazz, obj, "getPurchaseToken"); + return pendingPurchaseUpdate; +} + +std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj) { + jclass clazz = env->GetObjectClass(productsListObj); + jmethodID alistGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, clazz, productsListObj, "size"); + std::vector purchases; + for (int i = 0; i < size; ++i) { + jobject purchaseObj = env->CallObjectMethod(productsListObj, alistGetMethod, i); + cc::Purchase* purchase = cc::JniBillingHelper::toPurchase(env, purchaseObj); + purchases.push_back(purchase); + } + return std::move(purchases); +} + +cc::Purchase* JniBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { + jclass clazz = env->GetObjectClass(purchaseObj); + auto* purchase = new cc::Purchase; + + jmethodID getAccountIdentifiers = env->GetMethodID(clazz, "getAccountIdentifiers", "()Lcom/android/billingclient/api/AccountIdentifiers;"); + jobject accountIdentifiersObj = env->CallObjectMethod(purchaseObj, getAccountIdentifiers); + if (getAccountIdentifiers) { + purchase->accountIdentifiers.reset(toAccountIdentifiers(env, accountIdentifiersObj)); + } + + purchase->deleloperPayload = callStringMethod(env, clazz, purchaseObj, "getDeveloperPayload"); + purchase->orderId = callStringMethod(env, clazz, purchaseObj, "getOrderId"); + purchase->originalJson = callStringMethod(env, clazz, purchaseObj, "getOriginalJson"); + purchase->packageName = callStringMethod(env, clazz, purchaseObj, "getPackageName"); + + jmethodID getPendingPurchaseUpdate = env->GetMethodID(clazz, "getPendingPurchaseUpdate", "()Lcom/android/billingclient/api/Purchase$PendingPurchaseUpdate;"); + jobject pendingPurchaseUpdateObj = env->CallObjectMethod(purchaseObj, getPendingPurchaseUpdate); + if (pendingPurchaseUpdateObj) { + purchase->pendingPurchaseUpdate.reset(toPendingPurchaseUpdate(env, pendingPurchaseUpdateObj)); + } + + jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(purchaseObj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + auto& products = purchase->products; + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + purchase->purchaseState = callIntMethod(env, clazz, purchaseObj, "getPurchaseState"); + purchase->purchaseTime = callLongMethod(env, clazz, purchaseObj, "getPurchaseTime"); + purchase->purchaseToken = callStringMethod(env, clazz, purchaseObj, "getPurchaseToken"); + purchase->quantity = callIntMethod(env, clazz, purchaseObj, "getPurchaseState"); + purchase->signature = callStringMethod(env, clazz, purchaseObj, "getSignature"); + purchase->hashCode = callIntMethod(env, clazz, purchaseObj, "hashCode"); + purchase->isAcknowledged = callBooleanMethod(env, clazz, purchaseObj, "isAcknowledged"); + purchase->isAutoRenewing = callBooleanMethod(env, clazz, purchaseObj, "isAutoRenewing"); + purchase->toString = callStringMethod(env, clazz, purchaseObj, "toString"); + return purchase; +} + +cc::BillingConfig* JniBillingHelper::toBillingConfig(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* billingConfig = new cc::BillingConfig; + billingConfig->countryCode = callStringMethod(env, clazz, obj, "getCountryCode"); + return billingConfig; +} + +cc::AlternativeBillingOnlyReportingDetails* JniBillingHelper::toAlternativeBillingOnlyReportingDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* alternativeBillingOnlyReportingDetails = new cc::AlternativeBillingOnlyReportingDetails; + alternativeBillingOnlyReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); + return alternativeBillingOnlyReportingDetails; +} + +cc::ExternalOfferReportingDetails* JniBillingHelper::toExternalOfferReportingDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* externalOfferReportingDetails = new cc::ExternalOfferReportingDetails; + externalOfferReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); + return externalOfferReportingDetails; +} + +cc::InAppMessageResult* JniBillingHelper::toInAppMessageResult(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* inAppMessageResult = new cc::InAppMessageResult; + inAppMessageResult->responseCode = callIntMethod(env, clazz, obj, "getResponseCode"); + inAppMessageResult->purchaseToken = callStringMethod(env, clazz, obj, "getPurchaseToken"); + return inAppMessageResult; +} + +} // namespace cc diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h new file mode 100644 index 00000000000..d8e3c53754c --- /dev/null +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h @@ -0,0 +1,113 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "base/Macros.h" + +namespace cc { +class BillingResult; +class ProductDetails; +class Purchase; +class OneTimePurchaseOfferDetails; +class ProductDetailsInstallmentPlanDetails; +class PricingPhase; +class PricingPhases; +class SubscriptionOfferDetails; +class AccountIdentifiers; +class PendingPurchaseUpdate; +class BillingConfig; +class AlternativeBillingOnlyReportingDetails; +class ExternalOfferReportingDetails; +class InAppMessageResult; + +class CC_DLL JniBillingHelper { +public: + static void startConnection(); + static void endConnection(); + static int getConnectionState(); + static bool isReady(); + static void queryProductDetailsParams(const std::vector &productIds, const std::string &type); + + static void launchBillingFlow(const std::vector &productDetailsList, const std::string &selectedOfferToken); + static void consumePurchases(const std::vector &purchases); + static void acknowledgePurchase(const std::vector &purchases); + static void queryPurchasesAsync(const std::string &productType); + static void getBillingConfigAsync(); + + static void createAlternativeBillingOnlyReportingDetailsAsync(); + static void isAlternativeBillingOnlyAvailableAsync(); + static void createExternalOfferReportingDetailsAsync(); + static void isExternalOfferAvailableAsync(); + static BillingResult *isFeatureSupported(const std::string &feature); + + static BillingResult *showAlternativeBillingOnlyInformationDialog(); + static BillingResult *showExternalOfferInformationDialog(); + static BillingResult *showInAppMessages(); + + static void onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onBillingServiceDisconnected(JNIEnv *env, jclass clazz); + static void onProductDetailsResponse(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj); + static void onPurchasesUpdated(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject purchaseListObj); + static void onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken); + static void onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject purchaseListObj); + static void onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj); + static void onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj); + static void onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj); + static void onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj); + +private: + static BillingResult *toBillingResult(JNIEnv *env, jobject obj); + static std::vector toProductDetailList(JNIEnv *env, jobject productsObj); + static std::vector toPurchaseList(JNIEnv *env, jobject productsObj); + static BillingConfig *toBillingConfig(JNIEnv *env, jobject billingConfigObj); + static AlternativeBillingOnlyReportingDetails *toAlternativeBillingOnlyReportingDetails(JNIEnv *env, jobject alternativeBillingOnlyReportingDetailsObj); + static ExternalOfferReportingDetails *toExternalOfferReportingDetails(JNIEnv *env, jobject externalOfferReportingDetailsObj); + static InAppMessageResult *toInAppMessageResult(JNIEnv *env, jobject inAppMessageResultObj); + static ProductDetails *toProductDetail(JNIEnv *env, jobject productObj); + static Purchase *toPurchase(JNIEnv *env, jobject purchaseObj); + static OneTimePurchaseOfferDetails *toOneTimePurchaseOfferDetails(JNIEnv *env, jobject obj); + static ProductDetailsInstallmentPlanDetails *toProductDetailsInstallmentPlanDetails(JNIEnv *env, jobject obj); + static SubscriptionOfferDetails *toSubscriptionOfferDetails(JNIEnv *env, jobject obj); + static AccountIdentifiers *toAccountIdentifiers(JNIEnv *env, jobject obj); + static PendingPurchaseUpdate *toPendingPurchaseUpdate(JNIEnv *env, jobject obj); + static PricingPhase *toPricingPhase(JNIEnv *env, jobject obj); + static PricingPhases *toPricingPhases(JNIEnv* env, jobject obj); + static BillingResult *callFunctionAndReturnBillingResult(const std::string &functionName); +}; + +} // namespace cc diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.cpp b/native/cocos/platform/android/modules/google_play/billing/billing.cpp new file mode 100644 index 00000000000..3fe72a3854c --- /dev/null +++ b/native/cocos/platform/android/modules/google_play/billing/billing.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/android/modules/google_play/billing/billing.h" +#include "platform/android/modules/google_play/billing/JniBillingHelper.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +void Billing::startConnection() { + JniBillingHelper::startConnection(); +} + +void Billing::endConnection() { + JniBillingHelper::endConnection(); +} + +int Billing::getConnectionState() const { + return JniBillingHelper::getConnectionState(); +} + +bool Billing::isReady() const { + return JniBillingHelper::isReady(); +} + +void Billing::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { + JniBillingHelper::queryProductDetailsParams(productIds, type); +} + +void Billing::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { + JniBillingHelper::launchBillingFlow(productDetailsList, selectedOfferToken); +} + +void Billing::consumePurchases(const std::vector& purchases) { + JniBillingHelper::consumePurchases(purchases); +} + +void Billing::acknowledgePurchase(const std::vector& purchases) { + JniBillingHelper::acknowledgePurchase(purchases); +} + +void Billing::queryPurchasesAsync(const std::string& productType) { + JniBillingHelper::queryPurchasesAsync(productType); +} + +void Billing::getBillingConfigAsync() { + JniBillingHelper::getBillingConfigAsync(); +} + +BillingResult* Billing::isFeatureSupported(const std::string& feature) { + return JniBillingHelper::isFeatureSupported(feature); +} + +void Billing::createAlternativeBillingOnlyReportingDetailsAsync() { + JniBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync(); +} + +void Billing::isAlternativeBillingOnlyAvailableAsync() { + JniBillingHelper::isAlternativeBillingOnlyAvailableAsync(); +} + +void Billing::createExternalOfferReportingDetailsAsync() { + JniBillingHelper::createExternalOfferReportingDetailsAsync(); +} + +void Billing::isExternalOfferAvailableAsync() { + JniBillingHelper::isExternalOfferAvailableAsync(); +} + +BillingResult* Billing::showAlternativeBillingOnlyInformationDialog() { + return JniBillingHelper::showAlternativeBillingOnlyInformationDialog(); +} + +BillingResult* Billing::showExternalOfferInformationDialog() { + return JniBillingHelper::showExternalOfferInformationDialog(); +} + +BillingResult* Billing::showInAppMessages() { + return JniBillingHelper::showInAppMessages(); +} + +} // namespace cc diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.h b/native/cocos/platform/android/modules/google_play/billing/billing.h new file mode 100644 index 00000000000..e842fbd9dcd --- /dev/null +++ b/native/cocos/platform/android/modules/google_play/billing/billing.h @@ -0,0 +1,197 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "base/Macros.h" +#include "base/RefCounted.h" + +namespace cc { + +class CC_DLL BillingResult : public cc::RefCounted { +public: + int code{0}; + std::string debugMessage; + std::string toString; +}; + +class CC_DLL OneTimePurchaseOfferDetails { +public: + long priceAmountMicros; + std::string formattedPrice; + std::string priceCurrencyCode; +}; + +class CC_DLL ProductDetailsInstallmentPlanDetails { +public: + int installmentPlanCommitmentPaymentsCount; + int subsequentInstallmentPlanCommitmentPaymentsCount; +}; + +class CC_DLL PricingPhase { +public: + int billingCycleCount; + long priceAmountMicros; + int recurrenceMode; + std::string billingPeriod; + std::string formattedPrice; + std::string priceCurrencyCode; +}; + +class CC_DLL PricingPhases { +public: + ~PricingPhases() { + for (auto* ptr : pricingPhaseList) { + delete ptr; + } + pricingPhaseList.clear(); + } + std::vector pricingPhaseList; +}; + +class CC_DLL SubscriptionOfferDetails { +public: + std::string basePlanId; + std::string offerId; + std::string offerToken; + std::vector offerTags; + std::unique_ptr pricingPhases; + std::unique_ptr installmentPlanDetails; +}; + +class CC_DLL ProductDetails : public cc::RefCounted { +public: + ~ProductDetails() { + for (auto* ptr : subscriptionOfferDetails) { + delete ptr; + } + subscriptionOfferDetails.clear(); + } + bool equals(const ProductDetails& other) const { + return hashCode == other.hashCode; + } + int hashCode; + std::string description; + std::string name; + std::string productId; + std::string productType; + std::string title; + std::string toString; + std::unique_ptr oneTimePurchaseOfferDetails; + std::vector subscriptionOfferDetails; +}; + + +class CC_DLL AccountIdentifiers { +public: + std::string obfuscatedAccountId; + std::string obfuscatedProfileId; +}; + +class CC_DLL PendingPurchaseUpdate { +public: + std::string purchaseToken; + std::vector products; +}; + +class CC_DLL Purchase : public cc::RefCounted { +public: + bool equals(const Purchase& other) const { + return hashCode == other.hashCode; + } + + bool isAcknowledged; + bool isAutoRenewing; + int purchaseState; + int hashCode; + int quantity; + long purchaseTime; + std::string deleloperPayload; + std::string orderId; + std::string originalJson; + std::string packageName; + std::string purchaseToken; + std::string signature; + std::string toString; + std::unique_ptr accountIdentifiers; + std::unique_ptr pendingPurchaseUpdate; + std::vector products; +}; + +class CC_DLL BillingConfig : public cc::RefCounted { +public: + std::string countryCode; +}; + +class AlternativeBillingOnlyReportingDetails : public cc::RefCounted{ +public: + std::string externalTransactionToken; +}; + +class ExternalOfferReportingDetails : public cc::RefCounted{ +public: + std::string externalTransactionToken; +}; + +class InAppMessageResult : public cc::RefCounted{ +public: + int responseCode; + std::string purchaseToken; +}; + +class CC_DLL Billing { +public: + static Billing &getInstance() { + static Billing instance; + return instance; + } + + void startConnection(); + void endConnection(); + int getConnectionState() const; + bool isReady() const; + void queryProductDetailsParams(const std::vector& productIds, const std::string& type); + void launchBillingFlow(const std::vector& productDetails, const std::string& selectedOfferToken); + void consumePurchases(const std::vector& purchase); + void acknowledgePurchase(const Purchase* purchase); + void acknowledgePurchase(const std::vector& purchase); + void queryPurchasesAsync(const std::string& productType); + void getBillingConfigAsync(); + + void createAlternativeBillingOnlyReportingDetailsAsync(); + void isAlternativeBillingOnlyAvailableAsync(); + void createExternalOfferReportingDetailsAsync(); + void isExternalOfferAvailableAsync(); + BillingResult* isFeatureSupported(const std::string& feature); + + BillingResult* showAlternativeBillingOnlyInformationDialog(); + BillingResult* showExternalOfferInformationDialog(); + BillingResult* showInAppMessages(); +private: + Billing() = default; +}; + +} // namespace cc diff --git a/native/cocos/platform/java/jni/JniCocosBilling.cpp b/native/cocos/platform/java/jni/JniCocosBilling.cpp new file mode 100644 index 00000000000..de2fe2fe42b --- /dev/null +++ b/native/cocos/platform/java/jni/JniCocosBilling.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/JniHelper.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include + #include +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#endif + +#include +#include "cocos/base/UTF8.h" +#include "engine/EngineEvents.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/java/modules/SystemWindow.h" + +#include "base/memory/Memory.h" +#include "base/std/container/string.h" +#include "cocos/platform/android/modules/google_play/billing/JniBillingHelper.h" +#include "cocos/platform/android/modules/google_play/billing/billing.h" + +namespace cc { + +} + +extern "C" { + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onBillingSetupFinished(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { + cc::JniBillingHelper::onBillingServiceDisconnected(env, clazz); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj) { + cc::JniBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject purchaseListObj) { + cc::JniBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + cc::JniBillingHelper::onConsumeResponse(env, clazz, billingResultObj, purchaseToken); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, + jobject purchaseListObj) { + cc::JniBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onAcknowledgePurchaseResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + cc::JniBillingHelper::onBillingConfigResponse(env, clazz, billingResultObj, billingConfigObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + cc::JniBillingHelper::onAlternativeBillingOnlyTokenResponse(env, clazz, billingResultObj, alternativeBillingOnlyReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + cc::JniBillingHelper::onExternalOfferReportingDetailsResponse(env, clazz, billingResultObj, externalOfferReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onExternalOfferAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::JniBillingHelper::onExternalOfferInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { + cc::JniBillingHelper::onInAppMessageResponse(env, clazz, inAppMessageResultObj); +} +} From b189927ac0a73f474f968ff2309585f8dbc0cf35 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Mon, 11 Nov 2024 16:28:35 +0800 Subject: [PATCH 02/13] 1. Remove redundant code 2. Adjust the file name --- cocos/core/platform/billing/billing.ts | 66 ++++++++--- cocos/native-binding/index.ts | 4 +- native/CMakeLists.txt | 2 +- ...oogle_play_billing.cpp => jsb_billing.cpp} | 107 ++++-------------- .../bindings/manual/jsb_module_register.cpp | 4 +- .../src/com/cocos/lib/CocosBillingHelper.java | 3 +- .../platform/android/libcocos2dx/build.gradle | 4 +- .../google_play/billing/JniBillingHelper.cpp | 10 +- .../google_play/billing/JniBillingHelper.h | 4 +- .../modules/google_play/billing/billing.h | 10 +- .../platform/java/jni/JniCocosBilling.cpp | 14 +-- 11 files changed, 91 insertions(+), 137 deletions(-) rename native/cocos/bindings/manual/{jsb_google_play_billing.cpp => jsb_billing.cpp} (91%) diff --git a/cocos/core/platform/billing/billing.ts b/cocos/core/platform/billing/billing.ts index 72a4fd3b935..3b34e070a2f 100644 --- a/cocos/core/platform/billing/billing.ts +++ b/cocos/core/platform/billing/billing.ts @@ -1,3 +1,26 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ import { JSB } from 'internal:constants'; import { native } from '../../../native-binding'; import { EventTarget } from '../../event'; @@ -6,7 +29,7 @@ import { ccenum } from '../../value-types/enum'; declare const jsb: any; /** * @en Google play billing event type - * @zh 输入事件类型 + * @zh google play billing事件类型 */ export enum BillingEventType { /** @@ -83,10 +106,10 @@ export enum BillingEventType { ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', /** * @en - * Called to notify that query product details operation has finished. + * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. * * @zh - * 当通知查询产品详细信息操作已完成时调用。 + * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 */ EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', /** @@ -97,12 +120,12 @@ export enum BillingEventType { * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 */ ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', - /** + /** * @en - * Called to notify that query product details operation has finished. + * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. * * @zh - * 当通知查询产品详细信息操作已完成时调用。 + * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 */ EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', /** @@ -113,12 +136,12 @@ export enum BillingEventType { * 当仅替代Billing对话流程已完成时触发。 */ ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', - /** + /** * @en - * Called to notify that query product details operation has finished. + * Called to notify that the external offer information dialog flow is finished. * * @zh - * 当通知查询产品详细信息操作已完成时调用。 + * 当外部报价信息对话流程已完成时触发。 */ EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', /** @@ -222,7 +245,7 @@ export enum BillingResponseCode { * A user billing error occurred during processing. * * @zh - * 处理过程中出现用户计费错误。 + * 处理过程中出现用户billing错误。 */ BILLING_UNAVAILABLE = 3, /** @@ -288,7 +311,7 @@ export enum RecurrenceMode { * The billing plan payment recurs for infinite billing periods unless cancelled. * * @zh - * 除非取消,否则计费计划付款将无限期地重复。 + * 除非取消,否则billing计划付款将无限期地重复。 */ INFINITE_RECURRING = 1, /** @@ -296,7 +319,7 @@ export enum RecurrenceMode { * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. * * @zh - * 计费计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 + * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 */ FINITE_RECURRING = 2, /** @@ -304,7 +327,7 @@ export enum RecurrenceMode { * The billing plan payment is a one time charge that does not repeat. * * @zh - * 计费计划付款是一次性费用,不会重复。 + * Billing计划付款是一次性费用,不会重复。 */ NON_RECURRING = 3, } @@ -314,7 +337,7 @@ export enum RecurrenceMode { * Connection state of billing client. * * @zh - * BillingClient的连接状态 + * Billing client的连接状态 */ export enum ConnectionState { /** @@ -372,7 +395,7 @@ export enum FeatureType { * Get billing config. * * @zh - * 获取计费配置。。 + * 获取计费配置。 */ BILLING_CONFIG = 'ggg', /** @@ -603,14 +626,14 @@ export class GooglePlayBilling { /** * @en Get the current billing client connection state. - * @zh 获取当前计费客户端连接状态。 + * @zh 获取当前billing客户端连接状态。 */ public getConnectionState (): number { if (jsb.billing) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return jsb.billing.getConnectionState(); } - return 0; + return ConnectionState.DISCONNECTED; } /** @@ -630,6 +653,9 @@ export class GooglePlayBilling { /** * @en Performs a network query the details of products available for sale in your app. * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + * @param productId @zh 产品ID。 @en product id. + * @param productType @zh 产品类型。 @en product type. + * */ public queryProductDetailsParams (productId: string[] | string, productType: ProductType): void { if (productId instanceof Array) { @@ -644,6 +670,8 @@ export class GooglePlayBilling { /** * @en Initiates the billing flow for an in-app purchase or subscription. * @zh 启动应用内购买或订阅的计费流程。 + * @param productDetails @zh 产品详情。 @en product details. + * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. */ public launchBillingFlow (productDetails: native.ProductDetails[] | native.ProductDetails, selectedOfferToken: string | null): void { if (productDetails instanceof Array) { @@ -658,6 +686,7 @@ export class GooglePlayBilling { /** * @en Consumes a given in-app product. * @zh 消费指定的应用内产品。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. */ public consumePurchases (purchase: native.Purchase[] | native.Purchase): void { if (purchase instanceof Array) { @@ -672,6 +701,7 @@ export class GooglePlayBilling { /** * @en Acknowledges in-app purchases. * @zh 确认应用内购买。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. */ public acknowledgePurchase (purchase: native.Purchase[] | native.Purchase): void { if (purchase instanceof Array) { @@ -686,6 +716,7 @@ export class GooglePlayBilling { /** * @en Returns purchases details for currently owned items bought within your app. * @zh 返回您应用内当前拥有的购买商品的购买详情。 + * @param productType @zh 产品类型 @en Product type. */ public queryPurchasesAsync (productType: ProductType): void { jsb.billing?.queryPurchasesAsync(productType); @@ -734,6 +765,7 @@ export class GooglePlayBilling { /** * @en Checks if the specified feature or capability is supported by the Play Store. * @zh 检查 Play Store 是否支持指定的功能。 + * @param feature @zh 功能特性 @en feature. */ public isFeatureSupported (feature: string): native.BillingResult | null { if (jsb.billing) { diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index bab664bace8..d1d1afb35d7 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1521,7 +1521,7 @@ export declare namespace native { * @en Represents additional details of an installment subscription plan. * @zh 表示分期付款订阅计划的附加详细信息。 */ - export interface ProductDetailsInstallmentPlanDetails { + export interface InstallmentPlanDetails { /** * @en Committed payments count after a user signs up for this subscription plan. * @zh 用户注册此订阅计划后承诺的付款数量。 @@ -1605,7 +1605,7 @@ export declare namespace native { * @en The additional details of an installment plan. * @zh 分期付款计划的附加详细信息。 */ - readonly installmentPlanDetails: ProductDetailsInstallmentPlanDetails; + readonly installmentPlanDetails: InstallmentPlanDetails; } /** * @en Represents the details of a one time or subscription product. diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index a6b62874eec..ce3695726c3 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -2567,7 +2567,7 @@ cocos_source_files( cocos/bindings/manual/jsb_pipeline_manual.h cocos/bindings/manual/jsb_pipeline_manual.cpp cocos/bindings/manual/jsb_adpf.cpp - cocos/bindings/manual/jsb_google_play_billing.cpp + cocos/bindings/manual/jsb_billing.cpp ) if(USE_AUDIO) cocos_source_files( diff --git a/native/cocos/bindings/manual/jsb_google_play_billing.cpp b/native/cocos/bindings/manual/jsb_billing.cpp similarity index 91% rename from native/cocos/bindings/manual/jsb_google_play_billing.cpp rename to native/cocos/bindings/manual/jsb_billing.cpp index b84e76259fc..901260d731e 100644 --- a/native/cocos/bindings/manual/jsb_google_play_billing.cpp +++ b/native/cocos/bindings/manual/jsb_billing.cpp @@ -32,7 +32,7 @@ #include "cocos/platform/android/modules/google_play/billing/billing.h" JSB_REGISTER_OBJECT_TYPE(cc::BillingResult); JSB_REGISTER_OBJECT_TYPE(cc::OneTimePurchaseOfferDetails); -JSB_REGISTER_OBJECT_TYPE(cc::ProductDetailsInstallmentPlanDetails); +JSB_REGISTER_OBJECT_TYPE(cc::InstallmentPlanDetails); JSB_REGISTER_OBJECT_TYPE(cc::PricingPhase); JSB_REGISTER_OBJECT_TYPE(cc::SubscriptionOfferDetails); JSB_REGISTER_OBJECT_TYPE(cc::ProductDetails); @@ -45,8 +45,6 @@ JSB_REGISTER_OBJECT_TYPE(cc::Billing); #endif #if CC_PLATFORM == CC_PLATFORM_ANDROID -se::Class* __jsb_cc_BillingResult_class = nullptr; -se::Object* __jsb_cc_BillingResult_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingResult) static bool js_cc_BillingResult_debugMessage_get(se::State& s) { CC_UNUSED bool ok = true; @@ -112,15 +110,10 @@ bool js_register_cc_BillingResult(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - - __jsb_cc_BillingResult_proto = cls->getProto(); - __jsb_cc_BillingResult_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_OneTimePurchaseOfferDetails_class = nullptr; -se::Object* __jsb_cc_OneTimePurchaseOfferDetails_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { @@ -183,22 +176,17 @@ bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - - __jsb_cc_OneTimePurchaseOfferDetails_proto = cls->getProto(); - __jsb_cc_OneTimePurchaseOfferDetails_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_ProductDetailsInstallmentPlanDetails_class = nullptr; -se::Object* __jsb_cc_ProductDetailsInstallmentPlanDetails_proto = nullptr; -SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetailsInstallmentPlanDetails) -static bool js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) +static bool js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { CC_UNUSED bool ok = true; - cc::ProductDetailsInstallmentPlanDetails* arg1 = (cc::ProductDetailsInstallmentPlanDetails*)NULL; + cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; int result; - arg1 = SE_THIS_OBJECT(s); + arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; result = arg1->installmentPlanCommitmentPaymentsCount; @@ -206,14 +194,14 @@ static bool js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitment return true; } -SE_BIND_PROP_GET(js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get) +SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get) -static bool js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { +static bool js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { CC_UNUSED bool ok = true; - cc::ProductDetailsInstallmentPlanDetails* arg1 = (cc::ProductDetailsInstallmentPlanDetails*)NULL; + cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; int result; - arg1 = SE_THIS_OBJECT(s); + arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; result = arg1->subsequentInstallmentPlanCommitmentPaymentsCount; @@ -221,33 +209,28 @@ static bool js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlan return true; } -SE_BIND_PROP_GET(js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get) +SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get) -static bool js_delete_cc_ProductDetailsInstallmentPlanDetails(se::State& s) { +static bool js_delete_cc_InstallmentPlanDetails(se::State& s) { return true; } -SE_BIND_FINALIZE_FUNC(js_delete_cc_ProductDetailsInstallmentPlanDetails) +SE_BIND_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) -bool js_register_cc_ProductDetailsInstallmentPlanDetails(se::Object* obj) { - auto* cls = se::Class::create("ProductDetailsInstallmentPlanDetails", obj, nullptr, nullptr); +bool js_register_cc_InstallmentPlanDetails(se::Object* obj) { + auto* cls = se::Class::create("InstallmentPlanDetails", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); - cls->defineProperty("installmentPlanCommitmentPaymentsCount", _SE(js_cc_ProductDetailsInstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get), nullptr); - cls->defineProperty("subsequentInstallmentPlanCommitmentPaymentsCount", _SE(js_cc_ProductDetailsInstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get), nullptr); + cls->defineProperty("installmentPlanCommitmentPaymentsCount", _SE(js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get), nullptr); + cls->defineProperty("subsequentInstallmentPlanCommitmentPaymentsCount", _SE(js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get), nullptr); - cls->defineFinalizeFunction(_SE(js_delete_cc_ProductDetailsInstallmentPlanDetails)); + cls->defineFinalizeFunction(_SE(js_delete_cc_InstallmentPlanDetails)); cls->install(); - JSBClassType::registerClass(cls); + JSBClassType::registerClass(cls); - __jsb_cc_ProductDetailsInstallmentPlanDetails_proto = cls->getProto(); - __jsb_cc_ProductDetailsInstallmentPlanDetails_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } - -se::Class* __jsb_cc_PricingPhase_class = nullptr; -se::Object* __jsb_cc_PricingPhase_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhase) static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { @@ -366,14 +349,10 @@ bool js_register_cc_PricingPhase(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_PricingPhase_proto = cls->getProto(); - __jsb_cc_PricingPhase_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_SubscriptionOfferDetails_class = nullptr; -se::Object* __jsb_cc_SubscriptionOfferDetails_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { @@ -396,7 +375,7 @@ SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_basePlanId_get) static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& s) { CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; - cc::ProductDetailsInstallmentPlanDetails* result = 0; + cc::InstallmentPlanDetails* result = 0; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; @@ -498,14 +477,10 @@ bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_SubscriptionOfferDetails_proto = cls->getProto(); - __jsb_cc_SubscriptionOfferDetails_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_ProductDetails_class = nullptr; -se::Object* __jsb_cc_ProductDetails_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetails) static bool js_cc_ProductDetails_equals(se::State& s) { @@ -710,14 +685,10 @@ bool js_register_cc_ProductDetails(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_ProductDetails_proto = cls->getProto(); - __jsb_cc_ProductDetails_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_AccountIdentifiers_class = nullptr; -se::Object* __jsb_cc_AccountIdentifiers_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { @@ -771,14 +742,10 @@ bool js_register_cc_AccountIdentifiers(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_AccountIdentifiers_proto = cls->getProto(); - __jsb_cc_AccountIdentifiers_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_PendingPurchaseUpdate_class = nullptr; -se::Object* __jsb_cc_PendingPurchaseUpdate_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { @@ -831,15 +798,10 @@ bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - - __jsb_cc_PendingPurchaseUpdate_proto = cls->getProto(); - __jsb_cc_PendingPurchaseUpdate_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_Purchase_class = nullptr; -se::Object* __jsb_cc_Purchase_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Purchase) static bool js_cc_Purchase_equals(se::State& s) { @@ -1130,28 +1092,13 @@ static bool js_cc_Purchase_toString_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_toString_get) -static bool js_new_cc_Purchase(se::State& s) // NOLINT(readability-identifier-naming) -{ - CC_UNUSED bool ok = true; - const auto& args = s.args(); - size_t argc = args.size(); - - cc::Purchase* result; - result = (cc::Purchase*)new cc::Purchase(); - - auto* ptr = JSB_MAKE_PRIVATE_OBJECT_WITH_INSTANCE(result); - s.thisObject()->setPrivateObject(ptr); - return true; -} -SE_BIND_CTOR(js_new_cc_Purchase, __jsb_cc_Purchase_class, js_delete_cc_Purchase) - static bool js_delete_cc_Purchase(se::State& s) { return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_Purchase) bool js_register_cc_Purchase(se::Object* obj) { - auto* cls = se::Class::create("Purchase", obj, nullptr, _SE(js_new_cc_Purchase)); + auto* cls = se::Class::create("Purchase", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); cls->defineProperty("accountIdentifiers", _SE(js_cc_Purchase_accountIdentifiers_get), nullptr); @@ -1178,8 +1125,6 @@ bool js_register_cc_Purchase(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_Purchase_proto = cls->getProto(); - __jsb_cc_Purchase_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } @@ -1206,8 +1151,6 @@ static bool js_delete_cc_BillingConfig(se::State& s) { } SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingConfig) -se::Class* __jsb_cc_BillingConfig_class = nullptr; -se::Object* __jsb_cc_BillingConfig_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingConfig) bool js_register_cc_BillingConfig(se::Object* obj) { @@ -1222,14 +1165,10 @@ bool js_register_cc_BillingConfig(se::Object* obj) { cls->install(); JSBClassType::registerClass(cls); - __jsb_cc_BillingConfig_proto = cls->getProto(); - __jsb_cc_BillingConfig_class = cls; se::ScriptEngine::getInstance()->clearException(); return true; } -se::Class* __jsb_cc_Billing_class = nullptr; -se::Object* __jsb_cc_Billing_proto = nullptr; SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Billing) static bool js_cc_Billing_startConnection(se::State& s) { @@ -1493,7 +1432,7 @@ bool js_register_cc_Billing(se::Object* obj) { return true; } -bool jsb_register_google_play_billing(se::Object* obj) { +bool jsb_register_all_billing(se::Object* obj) { se::Value nsVal; if (!obj->getProperty("jsb", &nsVal, true)) { se::HandleObject jsobj(se::Object::createPlainObject()); @@ -1504,7 +1443,7 @@ bool jsb_register_google_play_billing(se::Object* obj) { /* Register classes */ js_register_cc_BillingResult(ns); js_register_cc_OneTimePurchaseOfferDetails(ns); - js_register_cc_ProductDetailsInstallmentPlanDetails(ns); + js_register_cc_InstallmentPlanDetails(ns); js_register_cc_PricingPhase(ns); js_register_cc_SubscriptionOfferDetails(ns); @@ -1518,6 +1457,6 @@ bool jsb_register_google_play_billing(se::Object* obj) { } #else -void jsb_register_google_play_billing(se::Object *ns) {} // NOLINT +void jsb_register_all_billing(se::Object *ns) {} // NOLINT #endif // CC_PLATFORM_ANDROID diff --git a/native/cocos/bindings/manual/jsb_module_register.cpp b/native/cocos/bindings/manual/jsb_module_register.cpp index 94f92b6e65b..9bdc6299b12 100644 --- a/native/cocos/bindings/manual/jsb_module_register.cpp +++ b/native/cocos/bindings/manual/jsb_module_register.cpp @@ -125,7 +125,7 @@ #include "cocos/bindings/auto/jsb_physics_auto.h" #endif -extern bool jsb_register_google_play_billing(se::Object *); // NOLINT +extern bool jsb_register_all_billing(se::Object *); // NOLINT bool jsb_register_all_modules() { se::ScriptEngine *se = se::ScriptEngine::getInstance(); @@ -193,7 +193,7 @@ bool jsb_register_all_modules() { #endif #if CC_PLATFORM == CC_PLATFORM_ANDROID - se->addRegisterCallback(jsb_register_google_play_billing); + se->addRegisterCallback(jsb_register_all_billing); #endif #if CC_USE_MIDDLEWARE diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java index 1ba310dd4bb..b859b3e7860 100644 --- a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java +++ b/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java @@ -1,6 +1,5 @@ /**************************************************************************** -Copyright (c) 2024 - Xiamen Yaji Software Co., Ltd. +Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. http://www.cocos2d-x.org diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 540aa97b656..93aef3aa1a3 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -37,7 +37,5 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') - api "com.pranavpandey.android:dynamic-utils:4.6.1" - api "com.android.billingclient:billing:7.0.0" - implementation "com.pranavpandey.android:dynamic-support:6.4.0" + api "com.android.billingclient:billing:7.1.0" } diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp index 93185af8a25..1c170fb0976 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp @@ -357,9 +357,9 @@ cc::OneTimePurchaseOfferDetails* JniBillingHelper::toOneTimePurchaseOfferDetails return oneTimePurchaseOfferDetails; } -cc::ProductDetailsInstallmentPlanDetails* JniBillingHelper::toProductDetailsInstallmentPlanDetails(JNIEnv* env, jobject obj) { +cc::InstallmentPlanDetails* JniBillingHelper::toInstallmentPlanDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); - auto* installmentPlanDetails = new cc::ProductDetailsInstallmentPlanDetails; + auto* installmentPlanDetails = new cc::InstallmentPlanDetails; installmentPlanDetails->installmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "getInstallmentPlanCommitmentPaymentsCount"); installmentPlanDetails->subsequentInstallmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "subsequentInstallmentPlanCommitmentPaymentsCount"); return installmentPlanDetails; @@ -401,9 +401,9 @@ cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEn details->basePlanId = callStringMethod(env, clazz, obj, "getBasePlanId"); - jobject productDetailsInstallmentPlanDetailsObj = callObjectMethod(env, clazz, obj, "getInstallmentPlanDetails", "com/android/billingclient/api/ProductDetails$InstallmentPlanDetails;"); - if (productDetailsInstallmentPlanDetailsObj != nullptr) { - details->installmentPlanDetails.reset(toProductDetailsInstallmentPlanDetails(env, productDetailsInstallmentPlanDetailsObj)); + jobject installmentPlanDetailsObj = callObjectMethod(env, clazz, obj, "getInstallmentPlanDetails", "com/android/billingclient/api/ProductDetails$InstallmentPlanDetails;"); + if (installmentPlanDetailsObj != nullptr) { + details->installmentPlanDetails.reset(toInstallmentPlanDetails(env, installmentPlanDetailsObj)); } details->offerId = callStringMethod(env, clazz, obj, "getOfferId"); diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h index d8e3c53754c..90c11da7a4c 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h @@ -35,7 +35,7 @@ class BillingResult; class ProductDetails; class Purchase; class OneTimePurchaseOfferDetails; -class ProductDetailsInstallmentPlanDetails; +class InstallmentPlanDetails; class PricingPhase; class PricingPhases; class SubscriptionOfferDetails; @@ -101,7 +101,7 @@ class CC_DLL JniBillingHelper { static ProductDetails *toProductDetail(JNIEnv *env, jobject productObj); static Purchase *toPurchase(JNIEnv *env, jobject purchaseObj); static OneTimePurchaseOfferDetails *toOneTimePurchaseOfferDetails(JNIEnv *env, jobject obj); - static ProductDetailsInstallmentPlanDetails *toProductDetailsInstallmentPlanDetails(JNIEnv *env, jobject obj); + static InstallmentPlanDetails *toInstallmentPlanDetails(JNIEnv *env, jobject obj); static SubscriptionOfferDetails *toSubscriptionOfferDetails(JNIEnv *env, jobject obj); static AccountIdentifiers *toAccountIdentifiers(JNIEnv *env, jobject obj); static PendingPurchaseUpdate *toPendingPurchaseUpdate(JNIEnv *env, jobject obj); diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.h b/native/cocos/platform/android/modules/google_play/billing/billing.h index e842fbd9dcd..0178a46b270 100644 --- a/native/cocos/platform/android/modules/google_play/billing/billing.h +++ b/native/cocos/platform/android/modules/google_play/billing/billing.h @@ -45,7 +45,7 @@ class CC_DLL OneTimePurchaseOfferDetails { std::string priceCurrencyCode; }; -class CC_DLL ProductDetailsInstallmentPlanDetails { +class CC_DLL InstallmentPlanDetails { public: int installmentPlanCommitmentPaymentsCount; int subsequentInstallmentPlanCommitmentPaymentsCount; @@ -64,8 +64,8 @@ class CC_DLL PricingPhase { class CC_DLL PricingPhases { public: ~PricingPhases() { - for (auto* ptr : pricingPhaseList) { - delete ptr; + for (auto* pricingPhase : pricingPhaseList) { + delete pricingPhase; } pricingPhaseList.clear(); } @@ -79,7 +79,7 @@ class CC_DLL SubscriptionOfferDetails { std::string offerToken; std::vector offerTags; std::unique_ptr pricingPhases; - std::unique_ptr installmentPlanDetails; + std::unique_ptr installmentPlanDetails; }; class CC_DLL ProductDetails : public cc::RefCounted { @@ -176,7 +176,6 @@ class CC_DLL Billing { void queryProductDetailsParams(const std::vector& productIds, const std::string& type); void launchBillingFlow(const std::vector& productDetails, const std::string& selectedOfferToken); void consumePurchases(const std::vector& purchase); - void acknowledgePurchase(const Purchase* purchase); void acknowledgePurchase(const std::vector& purchase); void queryPurchasesAsync(const std::string& productType); void getBillingConfigAsync(); @@ -186,7 +185,6 @@ class CC_DLL Billing { void createExternalOfferReportingDetailsAsync(); void isExternalOfferAvailableAsync(); BillingResult* isFeatureSupported(const std::string& feature); - BillingResult* showAlternativeBillingOnlyInformationDialog(); BillingResult* showExternalOfferInformationDialog(); BillingResult* showInAppMessages(); diff --git a/native/cocos/platform/java/jni/JniCocosBilling.cpp b/native/cocos/platform/java/jni/JniCocosBilling.cpp index de2fe2fe42b..9f4d3a217ac 100644 --- a/native/cocos/platform/java/jni/JniCocosBilling.cpp +++ b/native/cocos/platform/java/jni/JniCocosBilling.cpp @@ -1,5 +1,5 @@ /**************************************************************************** - Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. http://www.cocos.com @@ -31,19 +31,7 @@ #endif #include -#include "cocos/base/UTF8.h" -#include "engine/EngineEvents.h" -#include "platform/java/jni/glue/JniNativeGlue.h" -#include "platform/java/modules/SystemWindow.h" - -#include "base/memory/Memory.h" -#include "base/std/container/string.h" #include "cocos/platform/android/modules/google_play/billing/JniBillingHelper.h" -#include "cocos/platform/android/modules/google_play/billing/billing.h" - -namespace cc { - -} extern "C" { From f76ea2c01e400219149fd8d015ee774d92e3f052 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Tue, 12 Nov 2024 09:28:19 +0800 Subject: [PATCH 03/13] Optimized code --- cocos/core/platform/billing/billing.ts | 36 ++-- cocos/native-binding/index.ts | 9 +- native/cocos/bindings/manual/jsb_billing.cpp | 197 +++++++++--------- .../platform/android/libcocos2dx/build.gradle | 4 +- .../google_play/billing/JniBillingHelper.cpp | 36 ++-- .../modules/google_play/billing/billing.h | 4 +- .../platform/java/jni/JniCocosBilling.cpp | 7 - 7 files changed, 142 insertions(+), 151 deletions(-) diff --git a/cocos/core/platform/billing/billing.ts b/cocos/core/platform/billing/billing.ts index 3b34e070a2f..377edd9cc14 100644 --- a/cocos/core/platform/billing/billing.ts +++ b/cocos/core/platform/billing/billing.ts @@ -154,7 +154,6 @@ export enum BillingEventType { IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', } -ccenum(BillingEventType); /** * @en @@ -213,7 +212,7 @@ export enum BillingResponseCode { * The app is not connected to the Play Store service via the Google Play Billing Library. * * @zh - * 该应用未通过 Google Play 结算库连接到 Play Store 服务。 + * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 */ SERVICE_DISCONNECTED = -1, /** @@ -511,17 +510,15 @@ export enum InAppMessageResponseCode { interface BillingEventMap { [BillingEventType.BILLING_SETUP_FINISHED]: (result: native.BillingResult) => void, [BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, - [BillingEventType.PRODUCT_DETAILS_RESPONSE]: (result: native.BillingResult, - productDetailsList: native.ProductDetails[]) => void, - [BillingEventType.PURCHASES_UPDATED]: (result: native.BillingResult, - purchases: native.Purchase[]) => void, - [BillingEventType.CONSUME_RESPONSE]: (result: native.BillingResult, - purchaseToken: string) => void, - [BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: native.BillingResult, - purchases: native.Purchase[]) => void, + [BillingEventType.PRODUCT_DETAILS_RESPONSE]: (result: native.BillingResult, productDetailsList: native.ProductDetails[]) => void, + [BillingEventType.PURCHASES_UPDATED]: (result: native.BillingResult, purchases: native.Purchase[]) => void, + [BillingEventType.CONSUME_RESPONSE]: (result: native.BillingResult, purchaseToken: string) => void, + [BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: native.BillingResult, purchases: native.Purchase[]) => void, [BillingEventType.BILLING_CONFIG_RESPONSE]: (result: native.BillingResult, config: native.BillingConfig) => void - [BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails) => void - [BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails) => void + [BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: + (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails) => void + [BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: + (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails) => void [BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void [BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void [BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: native.BillingResult) => void @@ -579,12 +576,12 @@ export class GooglePlayBilling { this._eventTarget.emit(BillingEventType.BILLING_CONFIG_RESPONSE, result, config); }; - jsb.onAlternativeBillingOnlyTokenResponse = (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails): void => { - this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, alternativeBillingOnlyReportingDetails); + jsb.onAlternativeBillingOnlyTokenResponse = (result: native.BillingResult, details: native.AlternativeBillingOnlyReportingDetails): void => { + this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); }; - jsb.onExternalOfferReportingDetailsResponse = (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails): void => { - this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, externalOfferReportingDetails); + jsb.onExternalOfferReportingDetailsResponse = (result: native.BillingResult, details: native.ExternalOfferReportingDetails): void => { + this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); }; jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: native.BillingResult): void => { @@ -731,7 +728,8 @@ export class GooglePlayBilling { } /** - * @en Creates alternative billing only purchase details that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @en Creates alternative billing only purchase details that can be used to report a transaction made + * via alternative billing without user choice to use Google Play billing. * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 */ public createAlternativeBillingOnlyReportingDetailsAsync (): void { @@ -826,10 +824,10 @@ export class GooglePlayBilling { /** * @en - * The singleton of the Input class, this singleton manages all events of input. include: touch, mouse, accelerometer, gamepad, handle, hmd and keyboard. + * Interface for google play blling module. * * @zh - * google play的blling支付模块的接口。 + * google play blling模块的接口。 * * @example */ diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index d1d1afb35d7..e0178cd8ba1 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1491,7 +1491,7 @@ export declare namespace native { * @en Code returned in In-app Billing API calls. * @zh 应用内结算 API 调用中返回的响应代码。 */ - readonly code: string; + readonly responseCode: string; readonly toStr: string; } @@ -1692,7 +1692,7 @@ export declare namespace native { /** * @en Represents an in-app billing purchase. - * @zh 代表应用内付费购买。 + * @zh 代表应用内billing购买。 */ export interface Purchase { /** @@ -1727,7 +1727,7 @@ export declare namespace native { * @en The payload specified when the purchase was acknowledged or consumed. * @zh 确认或消费购买时指定的有效负载。 */ - readonly deleloperPayload: string; + readonly developerPayload: string; /** * @en Returns a unique order identifier for the transaction. * @zh 交易的唯一订单标识符。 @@ -1787,7 +1787,8 @@ export declare namespace native { */ export interface AlternativeBillingOnlyReportingDetails { /** - * @en An external transaction token that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @en An external transaction token that can be used to report a transaction made via alternative billing + * without user choice to use Google Play billing. * @zh 返回一个外部交易令牌,该令牌可用于报告通过替代付款方式进行的交易,而无需用户选择使用 Google Play 付款方式。 */ readonly externalTransactionToken: string; diff --git a/native/cocos/bindings/manual/jsb_billing.cpp b/native/cocos/bindings/manual/jsb_billing.cpp index 901260d731e..cbc3fd77c03 100644 --- a/native/cocos/bindings/manual/jsb_billing.cpp +++ b/native/cocos/bindings/manual/jsb_billing.cpp @@ -46,7 +46,7 @@ JSB_REGISTER_OBJECT_TYPE(cc::Billing); #if CC_PLATFORM == CC_PLATFORM_ANDROID SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingResult) -static bool js_cc_BillingResult_debugMessage_get(se::State& s) { +static bool js_cc_BillingResult_debugMessage_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* arg1 = (cc::BillingResult*)NULL; std::string result; @@ -63,21 +63,21 @@ static bool js_cc_BillingResult_debugMessage_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_BillingResult_debugMessage_get) -static bool js_cc_BillingResult_code_get(se::State& s) { +static bool js_cc_BillingResult_responseCode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* arg1 = (cc::BillingResult*)NULL; int result; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; - result = arg1->code; + result = arg1->responseCode; ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); return true; } -SE_BIND_PROP_GET(js_cc_BillingResult_code_get) +SE_BIND_PROP_GET(js_cc_BillingResult_responseCode_get) -static bool js_cc_BillingResult_toString_get(se::State& s) { +static bool js_cc_BillingResult_toString_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* arg1 = (cc::BillingResult*)NULL; std::string result; @@ -93,17 +93,17 @@ static bool js_cc_BillingResult_toString_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_BillingResult_toString_get) -static bool js_delete_cc_BillingResult(se::State& s) { +static bool js_delete_cc_BillingResult(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingResult) -bool js_register_cc_BillingResult(se::Object* obj) { +bool js_register_cc_BillingResult(se::Object* obj) { // NOLINT auto* cls = se::Class::create("BillingResult", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); cls->defineProperty("debugMessage", _SE(js_cc_BillingResult_debugMessage_get), nullptr); - cls->defineProperty("code", _SE(js_cc_BillingResult_code_get), nullptr); + cls->defineProperty("responseCode", _SE(js_cc_BillingResult_responseCode_get), nullptr); cls->defineProperty("toStr", _SE(js_cc_BillingResult_toString_get), nullptr); cls->defineFinalizeFunction(_SE(js_delete_cc_BillingResult)); @@ -116,7 +116,7 @@ bool js_register_cc_BillingResult(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) -static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { +static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; @@ -131,7 +131,7 @@ static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_formattedPrice_get) -static bool js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get(se::State& s) { +static bool js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; @@ -144,7 +144,7 @@ static bool js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get(se::State& s } SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get) -static bool js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get(se::State& s) { +static bool js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; @@ -159,12 +159,12 @@ static bool js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get(se::State& s } SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get) -static bool js_delete_cc_OneTimePurchaseOfferDetails(se::State& s) { +static bool js_delete_cc_OneTimePurchaseOfferDetails(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) -bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { +bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { // NOLINT auto* cls = se::Class::create("OneTimePurchaseOfferDetails", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -181,7 +181,7 @@ bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { } SE_DECLARE_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) -static bool js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { +static bool js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; int result; @@ -196,7 +196,7 @@ static bool js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_ } SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get) -static bool js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { +static bool js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; int result; @@ -211,12 +211,12 @@ static bool js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaym } SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get) -static bool js_delete_cc_InstallmentPlanDetails(se::State& s) { +static bool js_delete_cc_InstallmentPlanDetails(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) -bool js_register_cc_InstallmentPlanDetails(se::Object* obj) { +bool js_register_cc_InstallmentPlanDetails(se::Object* obj) { // NOLINT auto* cls = se::Class::create("InstallmentPlanDetails", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -233,7 +233,7 @@ bool js_register_cc_InstallmentPlanDetails(se::Object* obj) { } SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhase) -static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { +static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; int result; @@ -248,7 +248,7 @@ static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_billingCycleCount_get) -static bool js_cc_PricingPhase_priceAmountMicros_get(se::State& s) { +static bool js_cc_PricingPhase_priceAmountMicros_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; long result; @@ -263,7 +263,7 @@ static bool js_cc_PricingPhase_priceAmountMicros_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_priceAmountMicros_get) -static bool js_cc_PricingPhase_recurrenceMode_get(se::State& s) { +static bool js_cc_PricingPhase_recurrenceMode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; int result; @@ -278,7 +278,7 @@ static bool js_cc_PricingPhase_recurrenceMode_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_recurrenceMode_get) -static bool js_cc_PricingPhase_billingPeriod_get(se::State& s) { +static bool js_cc_PricingPhase_billingPeriod_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; std::string result; @@ -295,7 +295,7 @@ static bool js_cc_PricingPhase_billingPeriod_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_billingPeriod_get) -static bool js_cc_PricingPhase_formattedPrice_get(se::State& s) { +static bool js_cc_PricingPhase_formattedPrice_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; std::string result; @@ -312,7 +312,7 @@ static bool js_cc_PricingPhase_formattedPrice_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_formattedPrice_get) -static bool js_cc_PricingPhase_priceCurrencyCode_get(se::State& s) { +static bool js_cc_PricingPhase_priceCurrencyCode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; std::string result; @@ -329,12 +329,12 @@ static bool js_cc_PricingPhase_priceCurrencyCode_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PricingPhase_priceCurrencyCode_get) -static bool js_delete_cc_PricingPhase(se::State& s) { +static bool js_delete_cc_PricingPhase(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_PricingPhase) -bool js_register_cc_PricingPhase(se::Object* obj) { +bool js_register_cc_PricingPhase(se::Object* obj) { // NOLINT auto* cls = se::Class::create("PricingPhase", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -355,7 +355,7 @@ bool js_register_cc_PricingPhase(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) -static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::string result; @@ -372,7 +372,7 @@ static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_basePlanId_get) -static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; cc::InstallmentPlanDetails* result = 0; @@ -389,7 +389,7 @@ static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_installmentPlanDetails_get) -static bool js_cc_SubscriptionOfferDetails_offerId_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_offerId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::string result; @@ -406,7 +406,7 @@ static bool js_cc_SubscriptionOfferDetails_offerId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerId_get) -static bool js_cc_SubscriptionOfferDetails_offerTags_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_offerTags_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::vector result; @@ -423,7 +423,7 @@ static bool js_cc_SubscriptionOfferDetails_offerTags_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerTags_get) -static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::string result; @@ -440,7 +440,7 @@ static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerToken_get) -static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { +static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::vector result; @@ -457,12 +457,12 @@ static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_pricingPhaseList_get) -static bool js_delete_cc_SubscriptionOfferDetails(se::State& s) { +static bool js_delete_cc_SubscriptionOfferDetails(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) -bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { +bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { // NOLINT auto* cls = se::Class::create("SubscriptionOfferDetails", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -483,7 +483,7 @@ bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetails) -static bool js_cc_ProductDetails_equals(se::State& s) { +static bool js_cc_ProductDetails_equals(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -506,7 +506,7 @@ static bool js_cc_ProductDetails_equals(se::State& s) { } SE_BIND_FUNC(js_cc_ProductDetails_equals) -static bool js_cc_ProductDetails_hashCode_get(se::State& s) { +static bool js_cc_ProductDetails_hashCode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; int result; @@ -523,7 +523,7 @@ static bool js_cc_ProductDetails_hashCode_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_hashCode_get) -static bool js_cc_ProductDetails_description_get(se::State& s) { +static bool js_cc_ProductDetails_description_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -540,7 +540,7 @@ static bool js_cc_ProductDetails_description_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_description_get) -static bool js_cc_ProductDetails_name_get(se::State& s) { +static bool js_cc_ProductDetails_name_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -557,7 +557,7 @@ static bool js_cc_ProductDetails_name_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_name_get) -static bool js_cc_ProductDetails_oneTimePurchaseOfferDetails_get(se::State& s) { +static bool js_cc_ProductDetails_oneTimePurchaseOfferDetails_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; cc::OneTimePurchaseOfferDetails* result = 0; @@ -574,7 +574,7 @@ static bool js_cc_ProductDetails_oneTimePurchaseOfferDetails_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_oneTimePurchaseOfferDetails_get) -static bool js_cc_ProductDetails_productId_get(se::State& s) { +static bool js_cc_ProductDetails_productId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -591,7 +591,7 @@ static bool js_cc_ProductDetails_productId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_productId_get) -static bool js_cc_ProductDetails_productType_get(se::State& s) { +static bool js_cc_ProductDetails_productType_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -608,7 +608,7 @@ static bool js_cc_ProductDetails_productType_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_productType_get) -static bool js_cc_ProductDetails_title_get(se::State& s) { +static bool js_cc_ProductDetails_title_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -625,7 +625,7 @@ static bool js_cc_ProductDetails_title_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_title_get) -static bool js_cc_ProductDetails_toString_get(se::State& s) { +static bool js_cc_ProductDetails_toString_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::string result; @@ -642,7 +642,7 @@ static bool js_cc_ProductDetails_toString_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_toString_get) -static bool js_cc_ProductDetails_subscriptionOfferDetails_get(se::State& s) { +static bool js_cc_ProductDetails_subscriptionOfferDetails_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; std::vector result; @@ -659,12 +659,12 @@ static bool js_cc_ProductDetails_subscriptionOfferDetails_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_ProductDetails_subscriptionOfferDetails_get) -static bool js_delete_cc_ProductDetails(se::State& s) { +static bool js_delete_cc_ProductDetails(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_ProductDetails) -bool js_register_cc_ProductDetails(se::Object* obj) { +bool js_register_cc_ProductDetails(se::Object* obj) { // NOLINT auto* cls = se::Class::create("ProductDetails", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -691,7 +691,7 @@ bool js_register_cc_ProductDetails(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) -static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { +static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; std::string result; @@ -708,7 +708,7 @@ static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedAccountId_get) -static bool js_cc_AccountIdentifiers_obfuscatedProfileId_get(se::State& s) { +static bool js_cc_AccountIdentifiers_obfuscatedProfileId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; std::string result; @@ -725,12 +725,12 @@ static bool js_cc_AccountIdentifiers_obfuscatedProfileId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedProfileId_get) -static bool js_delete_cc_AccountIdentifiers(se::State& s) { +static bool js_delete_cc_AccountIdentifiers(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) -bool js_register_cc_AccountIdentifiers(se::Object* obj) { +bool js_register_cc_AccountIdentifiers(se::Object* obj) { // NOLINT auto* cls = se::Class::create("AccountIdentifiers", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -748,7 +748,7 @@ bool js_register_cc_AccountIdentifiers(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) -static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { +static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; std::vector result; @@ -765,7 +765,7 @@ static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_products_get) -static bool js_cc_PendingPurchaseUpdate_purchaseToken_get(se::State& s) { +static bool js_cc_PendingPurchaseUpdate_purchaseToken_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; std::string result; @@ -782,12 +782,12 @@ static bool js_cc_PendingPurchaseUpdate_purchaseToken_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_purchaseToken_get) -static bool js_delete_cc_PendingPurchaseUpdate(se::State& s) { +static bool js_delete_cc_PendingPurchaseUpdate(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) -bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { +bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { // NOLINT auto* cls = se::Class::create("PendingPurchaseUpdate", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -804,7 +804,7 @@ bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Purchase) -static bool js_cc_Purchase_equals(se::State& s) { +static bool js_cc_Purchase_equals(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -832,7 +832,7 @@ static bool js_cc_Purchase_equals(se::State& s) { } SE_BIND_FUNC(js_cc_Purchase_equals) -static bool js_cc_Purchase_accountIdentifiers_get(se::State& s) { +static bool js_cc_Purchase_accountIdentifiers_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; cc::AccountIdentifiers* result = 0; @@ -849,14 +849,14 @@ static bool js_cc_Purchase_accountIdentifiers_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_accountIdentifiers_get) -static bool js_cc_Purchase_deleloperPayload_get(se::State& s) { +static bool js_cc_Purchase_developerPayload_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; - result = arg1->deleloperPayload; + result = arg1->developerPayload; ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); SE_PRECONDITION2(ok, false, "Error processing arguments"); @@ -864,9 +864,9 @@ static bool js_cc_Purchase_deleloperPayload_get(se::State& s) { return true; } -SE_BIND_PROP_GET(js_cc_Purchase_deleloperPayload_get) +SE_BIND_PROP_GET(js_cc_Purchase_developerPayload_get) -static bool js_cc_Purchase_orderId_get(se::State& s) { +static bool js_cc_Purchase_orderId_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -883,7 +883,7 @@ static bool js_cc_Purchase_orderId_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_orderId_get) -static bool js_cc_Purchase_originalJson_get(se::State& s) { +static bool js_cc_Purchase_originalJson_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -900,7 +900,7 @@ static bool js_cc_Purchase_originalJson_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_originalJson_get) -static bool js_cc_Purchase_packageName_get(se::State& s) { +static bool js_cc_Purchase_packageName_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -917,7 +917,7 @@ static bool js_cc_Purchase_packageName_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_packageName_get) -static bool js_cc_Purchase_pendingPurchaseUpdate_get(se::State& s) { +static bool js_cc_Purchase_pendingPurchaseUpdate_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; cc::PendingPurchaseUpdate* result = 0; @@ -934,7 +934,7 @@ static bool js_cc_Purchase_pendingPurchaseUpdate_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_pendingPurchaseUpdate_get) -static bool js_cc_Purchase_products_get(se::State& s) { +static bool js_cc_Purchase_products_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::vector result; @@ -951,7 +951,7 @@ static bool js_cc_Purchase_products_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_products_get) -static bool js_cc_Purchase_purchaseState_get(se::State& s) { +static bool js_cc_Purchase_purchaseState_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; int result; @@ -966,7 +966,7 @@ static bool js_cc_Purchase_purchaseState_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_purchaseState_get) -static bool js_cc_Purchase_purchaseTime_get(se::State& s) { +static bool js_cc_Purchase_purchaseTime_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; long result; @@ -981,7 +981,7 @@ static bool js_cc_Purchase_purchaseTime_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_purchaseTime_get) -static bool js_cc_Purchase_purchaseToken_get(se::State& s) { +static bool js_cc_Purchase_purchaseToken_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -998,7 +998,7 @@ static bool js_cc_Purchase_purchaseToken_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_purchaseToken_get) -static bool js_cc_Purchase_quantity_get(se::State& s) { +static bool js_cc_Purchase_quantity_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; int result; @@ -1013,7 +1013,7 @@ static bool js_cc_Purchase_quantity_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_quantity_get) -static bool js_cc_Purchase_signature_get(se::State& s) { +static bool js_cc_Purchase_signature_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -1030,7 +1030,7 @@ static bool js_cc_Purchase_signature_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_signature_get) -static bool js_cc_Purchase_hashCode_get(se::State& s) { +static bool js_cc_Purchase_hashCode_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; int result; @@ -1045,7 +1045,7 @@ static bool js_cc_Purchase_hashCode_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_hashCode_get) -static bool js_cc_Purchase_isAcknowledged_get(se::State& s) { +static bool js_cc_Purchase_isAcknowledged_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; bool result; @@ -1060,7 +1060,7 @@ static bool js_cc_Purchase_isAcknowledged_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_isAcknowledged_get) -static bool js_cc_Purchase_isAutoRenewing_get(se::State& s) { +static bool js_cc_Purchase_isAutoRenewing_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; bool result; @@ -1075,7 +1075,7 @@ static bool js_cc_Purchase_isAutoRenewing_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_isAutoRenewing_get) -static bool js_cc_Purchase_toString_get(se::State& s) { +static bool js_cc_Purchase_toString_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::Purchase* arg1 = (cc::Purchase*)NULL; std::string result; @@ -1092,17 +1092,17 @@ static bool js_cc_Purchase_toString_get(se::State& s) { } SE_BIND_PROP_GET(js_cc_Purchase_toString_get) -static bool js_delete_cc_Purchase(se::State& s) { +static bool js_delete_cc_Purchase(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_Purchase) -bool js_register_cc_Purchase(se::Object* obj) { +bool js_register_cc_Purchase(se::Object* obj) { // NOLINT auto* cls = se::Class::create("Purchase", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); cls->defineProperty("accountIdentifiers", _SE(js_cc_Purchase_accountIdentifiers_get), nullptr); - cls->defineProperty("deleloperPayload", _SE(js_cc_Purchase_deleloperPayload_get), nullptr); + cls->defineProperty("developerPayload", _SE(js_cc_Purchase_developerPayload_get), nullptr); cls->defineProperty("orderId", _SE(js_cc_Purchase_orderId_get), nullptr); cls->defineProperty("originalJson", _SE(js_cc_Purchase_originalJson_get), nullptr); cls->defineProperty("packageName", _SE(js_cc_Purchase_packageName_get), nullptr); @@ -1129,7 +1129,7 @@ bool js_register_cc_Purchase(se::Object* obj) { return true; } -static bool js_cc_BillingConfig_countryCode(se::State& s) { +static bool js_cc_BillingConfig_countryCode(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingConfig* arg1 = (cc::BillingConfig*)NULL; std::string result; @@ -1146,14 +1146,14 @@ static bool js_cc_BillingConfig_countryCode(se::State& s) { } SE_BIND_PROP_GET(js_cc_BillingConfig_countryCode) -static bool js_delete_cc_BillingConfig(se::State& s) { +static bool js_delete_cc_BillingConfig(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingConfig) SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingConfig) -bool js_register_cc_BillingConfig(se::Object* obj) { +bool js_register_cc_BillingConfig(se::Object* obj) { // NOLINT auto* cls = se::Class::create("BillingConfig", obj, nullptr, nullptr); cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); @@ -1171,19 +1171,19 @@ bool js_register_cc_BillingConfig(se::Object* obj) { SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Billing) -static bool js_cc_Billing_startConnection(se::State& s) { +static bool js_cc_Billing_startConnection(se::State& s) { // NOLINT cc::Billing::getInstance().startConnection(); return true; } SE_BIND_FUNC(js_cc_Billing_startConnection) -static bool js_cc_Billing_endConnection(se::State& s) { +static bool js_cc_Billing_endConnection(se::State& s) { // NOLINT cc::Billing::getInstance().endConnection(); return true; } SE_BIND_FUNC(js_cc_Billing_endConnection) -static bool js_cc_Billing_getConnectionState(se::State& s) { +static bool js_cc_Billing_getConnectionState(se::State& s) { // NOLINT int connectionState = cc::Billing::getInstance().getConnectionState(); s.rval().setFloat(connectionState); return true; @@ -1197,8 +1197,7 @@ static bool js_cc_Billing_isReady(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_isReady) - -static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { +static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1221,7 +1220,7 @@ static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_queryProductDetailsParams) -static bool js_cc_Billing_launchBillingFlow(se::State& s) { +static bool js_cc_Billing_launchBillingFlow(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1250,7 +1249,7 @@ static bool js_cc_Billing_launchBillingFlow(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_launchBillingFlow) -static bool js_cc_Billing_consumePurchases(se::State& s) { +static bool js_cc_Billing_consumePurchases(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1272,7 +1271,7 @@ static bool js_cc_Billing_consumePurchases(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_consumePurchases) -static bool js_cc_Billing_acknowledgePurchase(se::State& s) { +static bool js_cc_Billing_acknowledgePurchase(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1294,7 +1293,7 @@ static bool js_cc_Billing_acknowledgePurchase(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_acknowledgePurchase) -static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { +static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1316,37 +1315,37 @@ static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_queryPurchasesAsync) -static bool js_cc_Billing_getBillingConfigAsync(se::State& s) { +static bool js_cc_Billing_getBillingConfigAsync(se::State& s) { // NOLINT cc::Billing::getInstance().getBillingConfigAsync(); return true; } SE_BIND_FUNC(js_cc_Billing_getBillingConfigAsync) -static bool js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { +static bool js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { // NOLINT cc::Billing::getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); return true; } SE_BIND_FUNC(js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync) -static bool js_cc_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { +static bool js_cc_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { // NOLINT cc::Billing::getInstance().isAlternativeBillingOnlyAvailableAsync(); return true; } SE_BIND_FUNC(js_cc_Billing_isAlternativeBillingOnlyAvailableAsync) -static bool js_cc_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { +static bool js_cc_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { // NOLINT cc::Billing::getInstance().createExternalOfferReportingDetailsAsync(); return true; } SE_BIND_FUNC(js_cc_Billing_createExternalOfferReportingDetailsAsync) -static bool js_cc_Billing_isExternalOfferAvailableAsync(se::State& s) { +static bool js_cc_Billing_isExternalOfferAvailableAsync(se::State& s) { // NOLINT cc::Billing::getInstance().isExternalOfferAvailableAsync(); return true; } SE_BIND_FUNC(js_cc_Billing_isExternalOfferAvailableAsync) -static bool js_cc_Billing_isFeatureSupported(se::State& s) { +static bool js_cc_Billing_isFeatureSupported(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1368,7 +1367,7 @@ static bool js_cc_Billing_isFeatureSupported(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_isFeatureSupported) -static bool js_cc_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { +static bool js_cc_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* result = cc::Billing::getInstance().showAlternativeBillingOnlyInformationDialog(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); @@ -1377,7 +1376,7 @@ static bool js_cc_Billing_showAlternativeBillingOnlyInformationDialog(se::State& } SE_BIND_FUNC(js_cc_Billing_showAlternativeBillingOnlyInformationDialog) -static bool js_cc_Billing_showExternalOfferInformationDialog(se::State& s) { +static bool js_cc_Billing_showExternalOfferInformationDialog(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* result = cc::Billing::getInstance().showExternalOfferInformationDialog(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); @@ -1386,7 +1385,7 @@ static bool js_cc_Billing_showExternalOfferInformationDialog(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_showExternalOfferInformationDialog) -static bool js_cc_Billing_showInAppMessages(se::State& s) { +static bool js_cc_Billing_showInAppMessages(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::BillingResult* result = cc::Billing::getInstance().showInAppMessages(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); @@ -1395,12 +1394,12 @@ static bool js_cc_Billing_showInAppMessages(se::State& s) { } SE_BIND_FUNC(js_cc_Billing_showInAppMessages) -static bool js_delete_cc_Billing(se::State& s) { +static bool js_delete_cc_Billing(se::State& s) { // NOLINT return true; } SE_BIND_FINALIZE_FUNC(js_delete_cc_Billing) -bool js_register_cc_Billing(se::Object* obj) { +bool js_register_cc_Billing(se::Object* obj) { // NOLINT se::Value billingVal{se::Object::createPlainObject()}; auto* cls = billingVal.toObject(); @@ -1432,7 +1431,7 @@ bool js_register_cc_Billing(se::Object* obj) { return true; } -bool jsb_register_all_billing(se::Object* obj) { +bool jsb_register_all_billing(se::Object* obj) { // NOLINT se::Value nsVal; if (!obj->getProperty("jsb", &nsVal, true)) { se::HandleObject jsobj(se::Object::createPlainObject()); diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 93aef3aa1a3..77b213c2115 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -6,8 +6,8 @@ android { defaultConfig { minSdkVersion PROP_MIN_SDK_VERSION targetSdkVersion PROP_TARGET_SDK_VERSION - versionCode 7 - versionName "7.0" + versionCode 1 + versionName "1.0" consumerProguardFiles 'proguard-rules.pro' } diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp index 1c170fb0976..71859c32062 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp @@ -343,7 +343,7 @@ cc::BillingResult* JniBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* billingResult = ccnew cc::BillingResult; billingResult->debugMessage = callStringMethod(env, clazz, obj, "getDebugMessage"); - billingResult->code = callIntMethod(env, clazz, obj, "getResponseCode"); + billingResult->responseCode = callIntMethod(env, clazz, obj, "getResponseCode"); billingResult->toString = callStringMethod(env, clazz, obj, "toString"); return billingResult; } @@ -385,10 +385,10 @@ cc::PricingPhases* JniBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { jmethodID methodId = env->GetMethodID(clazz, "getPricingPhaseList", "()Ljava/util/List;"); jobject listObj = env->CallObjectMethod(obj, methodId); jclass listClazz = env->GetObjectClass(listObj); - jmethodID alistGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, listClazz, listObj, "size"); for (int i = 0; i < size; ++i) { - jobject pricingPhaseObj = env->CallObjectMethod(listObj, alistGetMethod, i); + jobject pricingPhaseObj = env->CallObjectMethod(listObj, listGetMethod, i); cc::PricingPhase* pricingPhase = toPricingPhase(env, pricingPhaseObj); pricingPhases->pricingPhaseList.push_back(pricingPhase); } @@ -410,10 +410,10 @@ cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEn jmethodID methodId = env->GetMethodID(clazz, "getOfferTags", "()Ljava/util/List;"); jobject listObj = env->CallObjectMethod(obj, methodId); jclass listClazz = env->GetObjectClass(listObj); - jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, listClazz, listObj, "size"); for (int i = 0; i < size; ++i) { - jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); details->offerTags.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); } details->offerToken = callStringMethod(env, clazz, obj, "getOfferToken"); @@ -425,11 +425,11 @@ cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEn std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj) { jclass clazz = env->GetObjectClass(productListObj); - jmethodID alistGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productListObj, "size"); std::vector productDetailsList; for (int i = 0; i < size; ++i) { - jobject productDetailObj = env->CallObjectMethod(productListObj, alistGetMethod, i); + jobject productDetailObj = env->CallObjectMethod(productListObj, listGetMethod, i); cc::ProductDetails* productDetails = cc::JniBillingHelper::toProductDetail(env, productDetailObj); productDetailsList.push_back(productDetails); } @@ -458,11 +458,11 @@ cc::ProductDetails* JniBillingHelper::toProductDetail(JNIEnv* env, jobject produ jobject listObj = env->CallObjectMethod(productObj, getSubscriptionOfferDetails); if (listObj != nullptr) { jclass listClazz = env->GetObjectClass(listObj); - jmethodID alistGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, listClazz, listObj, "size"); std::vector details; for (int i = 0; i < size; ++i) { - jobject subscriptionOfferDetailsObj = env->CallObjectMethod(listObj, alistGetMethod, i); + jobject subscriptionOfferDetailsObj = env->CallObjectMethod(listObj, listGetMethod, i); cc::SubscriptionOfferDetails* detail = toSubscriptionOfferDetails(env, subscriptionOfferDetailsObj); productDetails->subscriptionOfferDetails.push_back(detail); } @@ -485,10 +485,10 @@ cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/ArrayList;"); jobject listObj = env->CallObjectMethod(obj, methodId); jclass listClazz = env->GetObjectClass(listObj); - jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/String;"); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/String;"); int size = callIntMethod(env, listClazz, listObj, "size"); for (int i = 0; i < size; ++i) { - jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); pendingPurchaseUpdate->products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); } pendingPurchaseUpdate->purchaseToken = callStringMethod(env, clazz, obj, "getPurchaseToken"); @@ -497,11 +497,11 @@ cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj) { jclass clazz = env->GetObjectClass(productsListObj); - jmethodID alistGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productsListObj, "size"); std::vector purchases; for (int i = 0; i < size; ++i) { - jobject purchaseObj = env->CallObjectMethod(productsListObj, alistGetMethod, i); + jobject purchaseObj = env->CallObjectMethod(productsListObj, listGetMethod, i); cc::Purchase* purchase = cc::JniBillingHelper::toPurchase(env, purchaseObj); purchases.push_back(purchase); } @@ -514,11 +514,11 @@ cc::Purchase* JniBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { jmethodID getAccountIdentifiers = env->GetMethodID(clazz, "getAccountIdentifiers", "()Lcom/android/billingclient/api/AccountIdentifiers;"); jobject accountIdentifiersObj = env->CallObjectMethod(purchaseObj, getAccountIdentifiers); - if (getAccountIdentifiers) { + if (accountIdentifiersObj) { purchase->accountIdentifiers.reset(toAccountIdentifiers(env, accountIdentifiersObj)); } - purchase->deleloperPayload = callStringMethod(env, clazz, purchaseObj, "getDeveloperPayload"); + purchase->developerPayload = callStringMethod(env, clazz, purchaseObj, "getDeveloperPayload"); purchase->orderId = callStringMethod(env, clazz, purchaseObj, "getOrderId"); purchase->originalJson = callStringMethod(env, clazz, purchaseObj, "getOriginalJson"); purchase->packageName = callStringMethod(env, clazz, purchaseObj, "getPackageName"); @@ -532,17 +532,17 @@ cc::Purchase* JniBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/List;"); jobject listObj = env->CallObjectMethod(purchaseObj, methodId); jclass listClazz = env->GetObjectClass(listObj); - jmethodID alist_get_method = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, listClazz, listObj, "size"); auto& products = purchase->products; for (int i = 0; i < size; ++i) { - jobject strObj = env->CallObjectMethod(listObj, alist_get_method, i); + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); } purchase->purchaseState = callIntMethod(env, clazz, purchaseObj, "getPurchaseState"); purchase->purchaseTime = callLongMethod(env, clazz, purchaseObj, "getPurchaseTime"); purchase->purchaseToken = callStringMethod(env, clazz, purchaseObj, "getPurchaseToken"); - purchase->quantity = callIntMethod(env, clazz, purchaseObj, "getPurchaseState"); + purchase->quantity = callIntMethod(env, clazz, purchaseObj, "getQuantity"); purchase->signature = callStringMethod(env, clazz, purchaseObj, "getSignature"); purchase->hashCode = callIntMethod(env, clazz, purchaseObj, "hashCode"); purchase->isAcknowledged = callBooleanMethod(env, clazz, purchaseObj, "isAcknowledged"); diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.h b/native/cocos/platform/android/modules/google_play/billing/billing.h index 0178a46b270..022ab816956 100644 --- a/native/cocos/platform/android/modules/google_play/billing/billing.h +++ b/native/cocos/platform/android/modules/google_play/billing/billing.h @@ -33,7 +33,7 @@ namespace cc { class CC_DLL BillingResult : public cc::RefCounted { public: - int code{0}; + int responseCode{0}; std::string debugMessage; std::string toString; }; @@ -129,7 +129,7 @@ class CC_DLL Purchase : public cc::RefCounted { int hashCode; int quantity; long purchaseTime; - std::string deleloperPayload; + std::string developerPayload; std::string orderId; std::string originalJson; std::string packageName; diff --git a/native/cocos/platform/java/jni/JniCocosBilling.cpp b/native/cocos/platform/java/jni/JniCocosBilling.cpp index 9f4d3a217ac..37e03deef1d 100644 --- a/native/cocos/platform/java/jni/JniCocosBilling.cpp +++ b/native/cocos/platform/java/jni/JniCocosBilling.cpp @@ -23,13 +23,6 @@ ****************************************************************************/ #include "platform/java/jni/JniHelper.h" -#if CC_PLATFORM == CC_PLATFORM_ANDROID - #include - #include -#elif CC_PLATFORM == CC_PLATFORM_OHOS - #include -#endif - #include #include "cocos/platform/android/modules/google_play/billing/JniBillingHelper.h" From 639b40bccf3975cc759d70d19cb110ac9043a60e Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Thu, 14 Nov 2024 16:18:58 +0800 Subject: [PATCH 04/13] Optimized code --- cocos/core/platform/billing/billing.ts | 2 - cocos/native-binding/index.ts | 19 +++++--- native/cocos/bindings/manual/jsb_billing.cpp | 44 +++++++++++++++++-- .../platform/android/libcocos2dx/build.gradle | 2 +- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/cocos/core/platform/billing/billing.ts b/cocos/core/platform/billing/billing.ts index 377edd9cc14..098e04cea08 100644 --- a/cocos/core/platform/billing/billing.ts +++ b/cocos/core/platform/billing/billing.ts @@ -24,7 +24,6 @@ import { JSB } from 'internal:constants'; import { native } from '../../../native-binding'; import { EventTarget } from '../../event'; -import { ccenum } from '../../value-types/enum'; declare const jsb: any; /** @@ -829,6 +828,5 @@ export class GooglePlayBilling { * @zh * google play blling模块的接口。 * - * @example */ export const googlePlayBilling = new GooglePlayBilling(); diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index e0178cd8ba1..c29f642decd 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1613,8 +1613,8 @@ export declare namespace native { */ export interface ProductDetails { /** - * @en Represents the available purchase plans to buy a subscription product. - * @zh 代表一次性购买产品的报价详情。 + * @en Hash code + * @zh hash值 */ readonly hashCode: number; /** @@ -1642,7 +1642,10 @@ export declare namespace native { * @zh 所售产品的标题。 */ readonly title: string; - + /** + * @en To string + * @zh 转换成字符串 + */ readonly toStr: string; /** * @en The offer details of an one-time purchase product. @@ -1715,7 +1718,10 @@ export declare namespace native { * @zh 指示订阅是否自动续订。 */ readonly isAutoRenewing: number; - + /** + * @en Hash code + * @zh hash值 + */ readonly hashCode: number; /** @@ -1753,7 +1759,10 @@ export declare namespace native { * @zh 包含使用开发者私钥签名的购买数据签名的字符串。 */ readonly signature: string; - + /** + * @en To string + * @zh 转换成字符串 + */ readonly toStr: string; /** diff --git a/native/cocos/bindings/manual/jsb_billing.cpp b/native/cocos/bindings/manual/jsb_billing.cpp index cbc3fd77c03..98e086c0633 100644 --- a/native/cocos/bindings/manual/jsb_billing.cpp +++ b/native/cocos/bindings/manual/jsb_billing.cpp @@ -353,6 +353,41 @@ bool js_register_cc_PricingPhase(se::Object* obj) { // NOLINT return true; } +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhases) +static bool js_cc_PricingPhases_pricingPhaseList_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pricingPhaseList; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhases_pricingPhaseList_get) +static bool js_delete_cc_PricingPhases(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PricingPhases) +bool js_register_cc_PricingPhases(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("PricingPhases", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("pricingPhaseList", _SE(js_cc_PricingPhases_pricingPhaseList_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_PricingPhases)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + SE_DECLARE_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { // NOLINT @@ -440,14 +475,14 @@ static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { // NOL } SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerToken_get) -static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { // NOLINT +static bool js_cc_SubscriptionOfferDetails_pricingPhases_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; std::vector result; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; - result = arg1->pricingPhases->pricingPhaseList; + result = arg1->pricingPhases; ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); SE_PRECONDITION2(ok, false, "Error processing arguments"); @@ -455,7 +490,7 @@ static bool js_cc_SubscriptionOfferDetails_pricingPhaseList_get(se::State& s) { return true; } -SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_pricingPhaseList_get) +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_pricingPhases_get) static bool js_delete_cc_SubscriptionOfferDetails(se::State& s) { // NOLINT return true; @@ -471,7 +506,7 @@ bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { // NOLINT cls->defineProperty("offerId", _SE(js_cc_SubscriptionOfferDetails_offerId_get), nullptr); cls->defineProperty("offerTags", _SE(js_cc_SubscriptionOfferDetails_offerTags_get), nullptr); cls->defineProperty("offerToken", _SE(js_cc_SubscriptionOfferDetails_offerToken_get), nullptr); - cls->defineProperty("pricingPhaseList", _SE(js_cc_SubscriptionOfferDetails_pricingPhaseList_get), nullptr); + cls->defineProperty("pricingPhases", _SE(js_cc_SubscriptionOfferDetails_pricingPhases_get), nullptr); cls->defineFinalizeFunction(_SE(js_delete_cc_SubscriptionOfferDetails)); cls->install(); @@ -1443,6 +1478,7 @@ bool jsb_register_all_billing(se::Object* obj) { // NOLINT js_register_cc_BillingResult(ns); js_register_cc_OneTimePurchaseOfferDetails(ns); js_register_cc_InstallmentPlanDetails(ns); + js_register_cc_PricingPhases(ns); js_register_cc_PricingPhase(ns); js_register_cc_SubscriptionOfferDetails(ns); diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 77b213c2115..4987034344f 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -37,5 +37,5 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') - api "com.android.billingclient:billing:7.1.0" + implementation "com.android.billingclient:billing:7.1.0" } From fd154259e546e10779463e6f363e1723de1cce44 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Tue, 26 Nov 2024 10:26:55 +0800 Subject: [PATCH 05/13] 1. increase the platform template of google play 2. add vendor module 3. Fix some problems of google play. --- @types/jsb.d.ts | 479 ++++++++++ cc.config.json | 3 + cocos/core/platform/billing/billing.ts | 832 ----------------- cocos/core/platform/index.ts | 11 - cocos/native-binding/index.ts | 360 +------- editor/engine-features/render-config.json | 9 + editor/engine-features/schema.json | 6 + editor/i18n/en/localization.js | 4 + editor/i18n/zh/localization.js | 4 + native/CMakeLists.txt | 50 +- native/cocos/bindings/manual/jsb_billing.cpp | 10 +- native/cocos/bindings/manual/jsb_global.cpp | 2 + .../bindings/manual/jsb_module_register.cpp | 2 +- .../com/cocos/billing}/CocosBilling.java | 0 .../cocos/billing}/CocosBillingHelper.java | 0 .../platform/android/libcocos2dx/build.gradle | 9 +- .../google_play/billing/JniBillingHelper.cpp | 37 +- .../google_play/billing/JniBillingHelper.h | 14 +- .../modules/google_play/billing/billing.cpp | 12 + .../modules/google_play/billing/billing.h | 11 +- .../platform/java/jni/JniCocosBilling.cpp | 44 +- .../native-pack-tool/source/base/manager.ts | 2 +- scripts/native-pack-tool/source/index.ts | 3 +- .../google-play/build/.idea/encodings.xml | 6 + templates/google-play/build/build.gradle | 18 + templates/google-play/build/gradle.properties | 77 ++ .../build/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + templates/google-play/build/gradlew | 164 ++++ templates/google-play/build/gradlew.bat | 90 ++ .../build/libservice/AndroidManifest.xml | 3 + .../google-play/build/libservice/build.gradle | 35 + .../build/libservice/proguard-rules.pro | 21 + .../src/com/cocos/service/SDKWrapper.java | 185 ++++ templates/google-play/build/settings.gradle | 10 + templates/google-play/template/.gitignore | 7 + templates/google-play/template/CMakeLists.txt | 15 + templates/google-play/template/app/.gitignore | 2 + .../template/app/AndroidManifest.xml | 42 + .../google-play/template/app/build.gradle | 113 +++ .../template/app/proguard-rules.pro | 54 ++ .../app/src/com/cocos/game/AppActivity.java | 125 +++ templates/google-play/template/build-cfg.json | 8 + templates/google-play/template/build.gradle | 28 + .../template/instantapp/AndroidManifest.xml | 53 ++ .../template/instantapp/build.gradle | 100 ++ .../template/instantapp/proguard-rules.pro | 54 ++ .../src/com/cocos/game/InstantActivity.java | 125 +++ .../template/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 6780 bytes .../template/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3964 bytes .../template/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 9915 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 16236 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 23478 bytes .../template/res/values/strings.xml | 2 + tsconfig.json | 3 +- typedoc-index.ts | 1 + vender/google/play/billing/billing.ts | 851 +++++++++++++++++ vendor/google/billing/billing.ts | 853 ++++++++++++++++++ 58 files changed, 3687 insertions(+), 1268 deletions(-) delete mode 100644 cocos/core/platform/billing/billing.ts rename native/cocos/platform/android/java/{src/com/cocos/lib => vendor/com/cocos/billing}/CocosBilling.java (100%) rename native/cocos/platform/android/java/{src/com/cocos/lib => vendor/com/cocos/billing}/CocosBillingHelper.java (100%) create mode 100644 templates/google-play/build/.idea/encodings.xml create mode 100644 templates/google-play/build/build.gradle create mode 100644 templates/google-play/build/gradle.properties create mode 100644 templates/google-play/build/gradle/wrapper/gradle-wrapper.jar create mode 100644 templates/google-play/build/gradle/wrapper/gradle-wrapper.properties create mode 100644 templates/google-play/build/gradlew create mode 100644 templates/google-play/build/gradlew.bat create mode 100644 templates/google-play/build/libservice/AndroidManifest.xml create mode 100644 templates/google-play/build/libservice/build.gradle create mode 100644 templates/google-play/build/libservice/proguard-rules.pro create mode 100644 templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java create mode 100644 templates/google-play/build/settings.gradle create mode 100644 templates/google-play/template/.gitignore create mode 100644 templates/google-play/template/CMakeLists.txt create mode 100644 templates/google-play/template/app/.gitignore create mode 100644 templates/google-play/template/app/AndroidManifest.xml create mode 100644 templates/google-play/template/app/build.gradle create mode 100644 templates/google-play/template/app/proguard-rules.pro create mode 100644 templates/google-play/template/app/src/com/cocos/game/AppActivity.java create mode 100644 templates/google-play/template/build-cfg.json create mode 100644 templates/google-play/template/build.gradle create mode 100644 templates/google-play/template/instantapp/AndroidManifest.xml create mode 100644 templates/google-play/template/instantapp/build.gradle create mode 100644 templates/google-play/template/instantapp/proguard-rules.pro create mode 100644 templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java create mode 100644 templates/google-play/template/res/mipmap-hdpi/ic_launcher.png create mode 100644 templates/google-play/template/res/mipmap-mdpi/ic_launcher.png create mode 100644 templates/google-play/template/res/mipmap-xhdpi/ic_launcher.png create mode 100644 templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 templates/google-play/template/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 templates/google-play/template/res/values/strings.xml create mode 100644 vender/google/play/billing/billing.ts create mode 100644 vendor/google/billing/billing.ts diff --git a/@types/jsb.d.ts b/@types/jsb.d.ts index d53fb8658a2..e2a593c05de 100644 --- a/@types/jsb.d.ts +++ b/@types/jsb.d.ts @@ -357,6 +357,485 @@ declare namespace jsb { readonly thermalStatusNormalized: number; onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; } | undefined; + + /** + * @en Params containing the response code and the debug message from In-app Billing API response. + * @zh 包含应用内结算 API 响应代码和调试信息的参数 + */ + export interface BillingResult { + /** + * @en Debug message returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的调试消息。 + */ + readonly debugMessage: string; + /** + * @en Code returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的响应代码。 + */ + readonly responseCode: string; + readonly toStr: string; + } + + /** + * @en Represents the offer details to buy an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface OneTimePurchaseOfferDetails { + /** + * @en The price for the payment in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 以微单位返回支付价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en Formatted price for the payment, including its currency sign. + * @zh 支付的格式化价格,包括其货币单位。 + */ + readonly formattedPrice: string; + /** + * @en ISO 4217 currency code for price. + * @zh 价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents additional details of an installment subscription plan. + * @zh 表示分期付款订阅计划的附加详细信息。 + */ + export interface InstallmentPlanDetails { + /** + * @en Committed payments count after a user signs up for this subscription plan. + * @zh 用户注册此订阅计划后承诺的付款数量。 + */ + readonly installmentPlanCommitmentPaymentsCount: number; + /** + * @en Subsequent committed payments count after this subscription plan renews. + * @zh 此订阅计划续订后的后续承诺付款数量。 + */ + readonly subsequentInstallmentPlanCommitmentPaymentsCount: number; + } + + /** + * @en Represents a pricing phase, describing how a user pays at a point in time. + * @zh 表示定价阶段,描述用户在某个时间点如何付款。 + */ + export interface PricingPhase { + /** + * @en Number of cycles for which the billing period is applied. + * @zh 计费周期适用的周期数。 + */ + readonly billingCycleCount: number; + /** + * @en The price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 微单位付款周期的价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en RecurrenceMode for the pricing phase. + * @zh 定价阶段的RecurrenceMode。 + */ + readonly recurrenceMode: number; + /** + * @en Billing period for which the given price applies, specified in ISO 8601 format. + * @zh 给定价格适用的计费期,以 ISO 8601 格式指定。 + */ + readonly billingPeriod: string; + /** + * @en Formatted price for the payment cycle, including its currency sign. + * @zh 付款周期的格式化价格,包括其货币符号。 + */ + readonly formattedPrice: string; + /** + * @en Returns ISO 4217 currency code for price. + * @zh 返回价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents the available purchase plans to buy a subscription product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface SubscriptionOfferDetails { + /** + * @en The base plan id associated with the subscription product. + * @zh 与订阅产品相关的基本计划 ID。 + */ + readonly basePlanId: string; + /** + * @en The offer id associated with the subscription product. + * @zh 与订阅产品相关的优惠 ID。 + */ + readonly offerId: string; + /** + * @en The offer tags associated with this Subscription Offer. + * @zh 与此订阅优惠相关的优惠标签。 + */ + readonly offerTags: string[]; + /** + * @en The offer token required to pass in launchBillingFlow to purchase the subscription product with these pricing phases. + * @zh 在 launchBillingFlow 中传递以使用这些定价阶段购买订阅产品所需的优惠令牌。 + */ + readonly offerToken: string; + /** + * @en The pricing phases for the subscription product. + * @zh 订阅产品的定价区间。 + */ + readonly pricingPhaseList: PricingPhase[]; + /** + * @en The additional details of an installment plan. + * @zh 分期付款计划的附加详细信息。 + */ + readonly installmentPlanDetails: InstallmentPlanDetails; + } + /** + * @en Represents the details of a one time or subscription product. + * @zh 代表一次性或订阅产品的详细信息。 + */ + export interface ProductDetails { + /** + * @en Hash code + * @zh hash值 + */ + readonly hashCode: number; + /** + * @en The description of the product. + * @zh 产品的描述。 + */ + readonly description: string; + /** + * @en The name of the product being sold. + * @zh 所售产品的名称。 + */ + readonly name: string; + /** + * @en The product's Id. + * @zh 产品的 ID。 + */ + readonly productId: string; + /** + * @en The ProductType of the product. + * @zh ProductType产品的。 + */ + readonly productType: string; + /** + * @en The title of the product being sold. + * @zh 所售产品的标题。 + */ + readonly title: string; + /** + * @en To string + * @zh 转换成字符串 + */ + readonly toStr: string; + /** + * @en The offer details of an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + readonly oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails; + /** + * @en A list containing all available offers to purchase a subscription product. + * @zh 返回包含购买订阅产品的所有可用优惠的列表。 + */ + readonly subscriptionOfferDetails: SubscriptionOfferDetails[]; + } + + /** + * @en Account identifiers that were specified when the purchase was made. + * @zh 购买时指定的帐户标识符。 + */ + export interface AccountIdentifiers { + /** + * @en The obfuscated account id specified in setObfuscatedAccountId. + * @zh 在setObfuscatedAccountId中设置的混淆账户id + */ + readonly obfuscatedAccountId: string; + /** + * @en The obfuscated profile id specified in setObfuscatedProfileId. + * @zh 在setObfuscatedProfileId中设置的混淆profile id + */ + readonly obfuscatedProfileId: string; + } + + /** + * @en Represents a pending change/update to the existing purchase. + * @zh 表示对现有购买的待定更改/更新。 + */ + export interface PendingPurchaseUpdate { + /** + * @en A token that uniquely identifies this pending transaction. + * @zh 唯一标识此待处理交易的令牌。 + */ + readonly purchaseToken: string; + /** + * @en The product ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + /** + * @en Represents an in-app billing purchase. + * @zh 代表应用内billing购买。 + */ + export interface Purchase { + /** + * @en One of PurchaseState indicating the state of the purchase. + * @zh PurchaseState表示购买状态的其中一个值。 + */ + readonly purchaseState: number; + /** + * @en The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970). + * @zh 产品购买的时间,以纪元(1970 年 1 月 1 日)以来的毫秒数表示。 + */ + readonly purchaseTime: number; + /** + * @en Indicates whether the purchase has been acknowledged. + * @zh 表示是否已确认购买。 + */ + readonly isAcknowledged: number; + /** + * @en Indicates whether the subscription renews automatically. + * @zh 指示订阅是否自动续订。 + */ + readonly isAutoRenewing: number; + /** + * @en Hash code + * @zh hash值 + */ + readonly hashCode: number; + + /** + * @en The quantity of the purchased product. + * @zh 购买产品的数量。 + */ + readonly quantity: number; + /** + * @en The payload specified when the purchase was acknowledged or consumed. + * @zh 确认或消费购买时指定的有效负载。 + */ + readonly developerPayload: string; + /** + * @en Returns a unique order identifier for the transaction. + * @zh 交易的唯一订单标识符。 + */ + readonly orderId: string; + /** + * @en Returns a String in JSON format that contains details about the purchase order. + * @zh 包含有关采购订单详细信息的 JSON 格式的字符串。 + */ + readonly originalJson: string; + /** + * @en The application package from which the purchase originated. + * @zh 购买来源的应用程序包。 + */ + readonly packageName: string; + /** + * @en A token that uniquely identifies a purchase for a given item and user pair. + * @zh 唯一标识给定商品和用户对的购买的令牌。 + */ + readonly purchaseToken: string; + /** + * @en String containing the signature of the purchase data that was signed with the private key of the developer. + * @zh 包含使用开发者私钥签名的购买数据签名的字符串。 + */ + readonly signature: string; + /** + * @en To string + * @zh 转换成字符串 + */ + readonly toStr: string; + + /** + * @en Returns account identifiers that were provided when the purchase was made. + * @zh 返回购买时提供的帐户标识符。 + */ + readonly accountIdentifiers: AccountIdentifiers; + /** + * @en The PendingPurchaseUpdate for an uncommitted transaction. + * @zh 返回PendingPurchaseUpdate未提交的事务。 + */ + readonly pendingPurchaseUpdate: PendingPurchaseUpdate; + /** + * @en the product Ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + export interface BillingConfig { + /** + * @en The customer's country code. + * @zh 客户的国家代码。 + */ + readonly countryCode: string; + } + + /** + * @en The details used to report transactions made via alternative billing without user choice to use Google Play billing. + * @zh 用于报告用户未选择使用 Google Play 结算方式而通过替代结算方式进行的交易的详细信息。 + */ + export interface AlternativeBillingOnlyReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via alternative billing + * without user choice to use Google Play billing. + * @zh 返回一个外部交易令牌,该令牌可用于报告通过替代付款方式进行的交易,而无需用户选择使用 Google Play 付款方式。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en The details used to report transactions made via external offer. + * @zh 用于报告通过外部报价进行的交易的详细信息。 + */ + export interface ExternalOfferReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via external offer. + * @zh 可用于报告通过外部报价进行的交易的外部交易令牌。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en Results related to in-app messaging. + * @zh 与应用程序内消息相关的结果。 + */ + export interface InAppMessageResult { + /** + * @en Response code for the in-app messaging API call. + * @zh 应用内消息传递 API 调用的响应代码。 + */ + readonly responseCode: number; + /** + * @en Token that identifies the purchase to be acknowledged, if any. + * @zh 返回标识需要确认的购买的令牌。 + */ + readonly purchaseToken: string; + } + + /** + * @en Main interface for communication between the google play library and user application code. + * @zh 产品 ID。 + */ + const billing: { + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + startConnection(): void; + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + endConnection(): void; + + /** + * @en Get the current billing client connection state. + * @zh 获取当前计费客户端连接状态。 + */ + getConnectionState(): number; + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + isReady(): boolean; + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + */ + queryProductDetailsParams(productId: string[], productType: string): void; + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + */ + launchBillingFlow(productDetails: ProductDetails[], selectedOfferToken: string | null): void; + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + */ + consumePurchases(purchases: Purchase[]): void; + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + */ + queryPurchasesAsync(productType: string): void; + /** + * @en Acknowledges in-app purchases.. + * @zh 确认应用内购买。 + */ + acknowledgePurchase(purchases: Purchase[]): void; + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + getBillingConfigAsync(): void; + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + */ + createAlternativeBillingOnlyReportingDetailsAsync(): void; + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + */ + isAlternativeBillingOnlyAvailableAsync(): void; + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + createExternalOfferReportingDetailsAsync(): void; + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + isExternalOfferAvailableAsync(): void; + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + */ + isFeatureSupported(feature: string): BillingResult; + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + showAlternativeBillingOnlyInformationDialog(): BillingResult; + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + showExternalOfferInformationDialog(): BillingResult; + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + showInAppMessages(): BillingResult; + } | undefined; + export let onBillingSetupFinished: (result: BillingResult) => void | undefined; + export let onBillingServiceDisconnected: () => void | undefined; + export let onProductDetailsResponse: (result: BillingResult, productDetailsList: ProductDetails[]) => void | undefined; + export let onPurchasesUpdated: (result: BillingResult, purchaseList: Purchase[]) => void | undefined; + export let onConsumeResponse: (result: BillingResult, purchaseToken: string) => void | undefined; + export let onAcknowledgePurchaseResponse: (result: BillingResult) => void | undefined; + export let onQueryPurchasesResponse: (result: BillingResult, purchaseList: Purchase[]) => void | undefined; + export let onBillingConfigResponse: (result: BillingResult, config: BillingConfig) => void | undefined; + export let onAlternativeBillingOnlyTokenResponse: (result: BillingResult, details: AlternativeBillingOnlyReportingDetails) => void | undefined; + export let onExternalOfferReportingDetailsResponse: (result: BillingResult, details: ExternalOfferReportingDetails) => void | undefined; + export let onAlternativeBillingOnlyAvailabilityResponse: (result: BillingResult) => void | undefined; + export let onExternalOfferAvailabilityResponse: (result: BillingResult) => void | undefined; + export let onAlternativeBillingOnlyInformationDialogResponse: (result: BillingResult) => void | undefined; + export let onExternalOfferInformationDialogResponse: (result: BillingResult) => void | undefined; + export let onInAppMessageResponse: (result: InAppMessageResult) => void | undefined; } declare namespace ns { diff --git a/cc.config.json b/cc.config.json index 4eae710d317..e8e06cd5091 100644 --- a/cc.config.json +++ b/cc.config.json @@ -159,6 +159,9 @@ "modules": ["tiled-map"], "dependentModules": ["2d"] }, + "vendor": { + "modules": ["vendor"] + }, "spine": { "modules": ["spine"], "dependentAssets": [ diff --git a/cocos/core/platform/billing/billing.ts b/cocos/core/platform/billing/billing.ts deleted file mode 100644 index 098e04cea08..00000000000 --- a/cocos/core/platform/billing/billing.ts +++ /dev/null @@ -1,832 +0,0 @@ -/**************************************************************************** - Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*****************************************************************************/ -import { JSB } from 'internal:constants'; -import { native } from '../../../native-binding'; -import { EventTarget } from '../../event'; - -declare const jsb: any; -/** - * @en Google play billing event type - * @zh google play billing事件类型 - */ -export enum BillingEventType { - /** - * @en - * Called to notify that setup is complete. - * - * @zh - * 当安装已经完成时触发。 - */ - BILLING_SETUP_FINISHED = 'billing_setup_finished', - /** - * @en - * Called to notify that the connection to the billing service was lost. - * - * @zh - * 当Billing服务连接断开时触发。 - */ - BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', - /** - * @en - * Listen to this event to get notifications of purchase updates. - * - * @zh - * 监听这个事件可以获取购买更新。 - */ - PURCHASES_UPDATED = 'purchases_updated', - /** - * @en - * Called to notify that query product details operation has finished. - * - * @zh - * 查询产品详细信息操作完成时触发。 - */ - PRODUCT_DETAILS_RESPONSE = 'product_details_response', - /** - * @en - * Called to notify that the query purchases operation has finished. - * - * @zh - * 查询购买操作完成时触发。 - */ - QUERY_PURCHASES_RESPONSE = 'query_purchases_response', - /** - * @en - * Called to notify that a consume operation has finished. - * - * @zh - * 消费操作完成时触发。 - */ - CONSUME_RESPONSE = 'consume_response', - /** - * @en - * Called to notify that an acknowledge purchase operation has finished. - * - * @zh - * 确认购买操作完成时触发。 - */ - ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', - /** - * @en - * Called to notify when the get billing config flow has finished. - * - * @zh - * 获取Billing配置流程完成时触发。 - */ - BILLING_CONFIG_RESPONSE = 'billing_config_response', - /** - * @en - * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. - * - * @zh - * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 - */ - ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', - /** - * @en - * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. - * - * @zh - * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 - */ - EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', - /** - * @en - * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. - * - * @zh - * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 - */ - ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', - /** - * @en - * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. - * - * @zh - * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 - */ - EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', - /** - * @en - * Called to notify that the alternative billing only dialog flow is finished. - * - * @zh - * 当仅替代Billing对话流程已完成时触发。 - */ - ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', - /** - * @en - * Called to notify that the external offer information dialog flow is finished. - * - * @zh - * 当外部报价信息对话流程已完成时触发。 - */ - EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', - /** - * @en - * Called to notify when the in-app messaging flow has finished. - * - * @zh - * 当应用内消息流程完成时触发。 - */ - IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', - -} - -/** - * @en - * Supported Product types. - * - * @zh - * 支持的产品类型。 - */ -export enum ProductType { - /** - * @en - * A Product type for Android apps in-app products. - * - * @zh - * Android 应用内产品的产品类型。 - */ - INAPP = 'inapp', - /** - * @en - * A Product type for Android apps subscriptions. - * - * @zh - * Android 应用程序订阅的产品类型。 - */ - SUBS = 'subs' -} - -/** - * @en - * Possible response codes. - * - * @zh - * 可能的响应代码。 - */ -export enum BillingResponseCode { - /** - * @en - * This field is deprecated. - * See SERVICE_UNAVAILABLE which will be used instead of this code. - * - * @zh - * 这个字段已经废弃。 - * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 - */ - SERVICE_TIMEOUT = -3, - /** - * @en - * The requested feature is not supported by the Play Store on the current device. - * - * @zh - * 当前设备上的 Play Store 不支持所请求的功能。 - */ - FEATURE_NOT_SUPPORTED = -2, - /** - * @en - * The app is not connected to the Play Store service via the Google Play Billing Library. - * - * @zh - * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 - */ - SERVICE_DISCONNECTED = -1, - /** - * @en - * Success. - * - * @zh - * 成功。 - */ - OK = 0, - /** - * @en - * Transaction was canceled by the user. - * - * @zh - * 交易已被用户取消。 - */ - USER_CANCELED = 1, - /** - * @en - * The service is currently unavailable. - * - * @zh - * 当前设备上的 Play Store 不支持所请求的功能。 - */ - SERVICE_UNAVAILABLE = 2, - /** - * @en - * A user billing error occurred during processing. - * - * @zh - * 处理过程中出现用户billing错误。 - */ - BILLING_UNAVAILABLE = 3, - /** - * @en - * The requested product is not available for purchase. - * - * @zh - * 所请求的产品无法购买。 - */ - ITEM_UNAVAILABLE = 4, - /** - * @en - * Error resulting from incorrect usage of the API. - * - * @zh - * 由于错误使用 API 而导致的错误。 - */ - DEVELOPER_ERROR = 5, - /** - * @en - * Fatal error during the API action. - * - * @zh - * API 操作期间发生致命错误。 - */ - ERROR = 6, - /** - * @en - * The purchase failed because the item is already owned. - * - * @zh - * 购买失败,因为该物品已被拥有。 - */ - ITEM_ALREADY_OWNED = 7, - /** - * @en - * Requested action on the item failed since it is not owned by the user. - * - * @zh - * 由于该项目不属于用户,因此对该项目请求的操作失败。 - */ - ITEM_NOT_OWNED = 8, - /** - * @en - * A network error occurred during the operation. - * - * @zh - * 操作期间发生网络错误。 - */ - NETWORK_ERROR = 12, -} - -/** - * @en - * Recurrence mode of the pricing phase. - * - * @zh - * 定价阶段的复现模式。 - */ -export enum RecurrenceMode { - /** - * @en - * The billing plan payment recurs for infinite billing periods unless cancelled. - * - * @zh - * 除非取消,否则billing计划付款将无限期地重复。 - */ - INFINITE_RECURRING = 1, - /** - * @en - * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. - * - * @zh - * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 - */ - FINITE_RECURRING = 2, - /** - * @en - * The billing plan payment is a one time charge that does not repeat. - * - * @zh - * Billing计划付款是一次性费用,不会重复。 - */ - NON_RECURRING = 3, -} - -/** - * @en - * Connection state of billing client. - * - * @zh - * Billing client的连接状态 - */ -export enum ConnectionState { - /** - * @en - * This client was not yet connected to billing service or was already closed. - * - * @zh - * 此客户端尚未连接到Billing服务或已关闭。 - */ - DISCONNECTED = 0, - /** - * @en - * This client is currently in process of connecting to billing service. - * - * @zh - * 此客户端目前正在连接到Billing服务。 - */ - CONNECTING = 1, - /** - * @en - * This client is currently connected to billing service. - * - * @zh - * 此客户端当前已连接到Billing服务。 - */ - CONNECTED = 2, - /** - * @en - * This client was already closed and shouldn't be used again. - * - * @zh - * 该客户端已关闭,不应再次使用。 - */ - CLOSED = 3, -} - -/** - * @en - * Features/capabilities supported by isFeatureSupported. - * - * @zh - * 支持的特性/能力isFeatureSupported。 - */ -export enum FeatureType { - /** - * @en - * Alternative billing only. - * - * @zh - * 仅限替代Billing。 - */ - ALTERNATIVE_BILLING_ONLY = 'jjj', - /** - * @en - * Get billing config. - * - * @zh - * 获取计费配置。 - */ - BILLING_CONFIG = 'ggg', - /** - * @en - * Play billing library support for external offer. - * - * @zh - * Play billing库支持外部报价。 - */ - EXTERNAL_OFFER = 'kkk', - /** - * @en - * Show in-app messages. - * - * @zh - * 显示应用内消息。 - */ - IN_APP_MESSAGING = 'bbb', - /** - * @en - * Launch a price change confirmation flow. - * - * @zh - * 启动价格变动确认流程。 - */ - PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', - /** - * @en - * Play billing library support for querying and purchasing. - * - * @zh - * Play Billing库支持查询、购买。 - */ - PRODUCT_DETAILS = 'fff', - /** - * @en - * Purchase/query for subscriptions. - * - * @zh - * 购买/查询订阅。 - */ - SUBSCRIPTIONS = 'subscriptions', - /** - * @en - * Subscriptions update/replace. - * - * @zh - * 订阅更新/替换。 - */ - UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', -} - -/** - * @en - * Possible purchase states. - * - * @zh - * 可能的购买状态。 - */ -export enum PurchaseState { - /** - * @en - * Purchase is pending and not yet completed to be processed by your app. - * - * @zh - * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 - */ - PENDING = 2, - /** - * @en - * Purchase is completed.. - * - * @zh - * 购买完成。 - */ - PURCHASED = 1, - /** - * @en - * Purchase with unknown state. - * - * @zh - * 未知状态 - */ - UNSPECIFIED_STATE = 0, -} - -/** - * @en - * Possible response codes. - * - * @zh - * InAppMessage可能的影响代码。 - */ -export enum InAppMessageResponseCode { - /** - * @en - * The flow has finished and there is no action needed from developers. - * - * @zh - * 流程已完成,开发人员无需采取任何行动。 - */ - NO_ACTION_NEEDED = 0, - /** - * @en - * The subscription status changed. - * - * @zh - * 订阅状态已改变。 - */ - SUBSCRIPTION_STATUS_UPDATED = 1, -} - -interface BillingEventMap { - [BillingEventType.BILLING_SETUP_FINISHED]: (result: native.BillingResult) => void, - [BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, - [BillingEventType.PRODUCT_DETAILS_RESPONSE]: (result: native.BillingResult, productDetailsList: native.ProductDetails[]) => void, - [BillingEventType.PURCHASES_UPDATED]: (result: native.BillingResult, purchases: native.Purchase[]) => void, - [BillingEventType.CONSUME_RESPONSE]: (result: native.BillingResult, purchaseToken: string) => void, - [BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: native.BillingResult, purchases: native.Purchase[]) => void, - [BillingEventType.BILLING_CONFIG_RESPONSE]: (result: native.BillingResult, config: native.BillingConfig) => void - [BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: - (result: native.BillingResult, alternativeBillingOnlyReportingDetails: native.AlternativeBillingOnlyReportingDetails) => void - [BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: - (result: native.BillingResult, externalOfferReportingDetails: native.ExternalOfferReportingDetails) => void - [BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void - [BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: native.BillingResult) => void - [BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: native.BillingResult) => void - [BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: native.BillingResult) => void - [BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: native.InAppMessageResult) => void -} - -export class GooglePlayBilling { - private _eventTarget: EventTarget = new EventTarget(); - constructor () { - if (!JSB || !jsb.billing) { - return; - } - jsb.onBillingSetupFinished = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.BILLING_SETUP_FINISHED, result); - }; - - jsb.onBillingServiceDisconnected = (): void => { - this._eventTarget.emit(BillingEventType.BILLING_SERVICE_DISCONNECTED); - }; - - jsb.onProductDetailsResponse = ( - result: native.BillingResult, - productDetailsList: native.ProductDetails[], - ): void => { - this._eventTarget.emit(BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); - }; - - jsb.onPurchasesUpdated = ( - result: native.BillingResult, - purchaseList: native.Purchase[], - ): void => { - this._eventTarget.emit(BillingEventType.PURCHASES_UPDATED, result, purchaseList); - }; - - jsb.onConsumeResponse = ( - result: native.BillingResult, - purchaseToken: string, - ): void => { - this._eventTarget.emit(BillingEventType.CONSUME_RESPONSE, result, purchaseToken); - }; - - jsb.onAcknowledgePurchaseResponse = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); - }; - - jsb.onQueryPurchasesResponse = ( - result: native.BillingResult, - purchaseList: native.Purchase[], - ): void => { - this._eventTarget.emit(BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); - }; - - jsb.onBillingConfigResponse = (result: native.BillingResult, config: native.BillingConfig): void => { - this._eventTarget.emit(BillingEventType.BILLING_CONFIG_RESPONSE, result, config); - }; - - jsb.onAlternativeBillingOnlyTokenResponse = (result: native.BillingResult, details: native.AlternativeBillingOnlyReportingDetails): void => { - this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); - }; - - jsb.onExternalOfferReportingDetailsResponse = (result: native.BillingResult, details: native.ExternalOfferReportingDetails): void => { - this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); - }; - - jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); - }; - - jsb.onExternalOfferAvailabilityResponse = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); - }; - - jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); - }; - - jsb.onExternalOfferInformationDialogResponse = (result: native.BillingResult): void => { - this._eventTarget.emit(BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); - }; - - jsb.onInAppMessageResponse = (result: native.InAppMessageResult): void => { - this._eventTarget.emit(BillingEventType.IN_APP_MESSAGE_RESPONSE, result); - }; - } - - /** - * @en Starts up BillingClient setup process asynchronously. - * @zh 异步启动 BillingClient 设置过程。 - */ - public startConnection (): void { - jsb.billing?.startConnection(); - } - - /** - * @en Closes the connection and releases all held resources such as service connections. - * @zh 关闭连接并释放所有持有的资源,例如服务连接。 - */ - public endConnection (): void { - jsb.billing?.endConnection(); - } - - /** - * @en Get the current billing client connection state. - * @zh 获取当前billing客户端连接状态。 - */ - public getConnectionState (): number { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.getConnectionState(); - } - return ConnectionState.DISCONNECTED; - } - - /** - * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. - Returns true if the client is currently connected to the service, false otherwise. - * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 - 如果客户端当前已连接到服务,则返回 true,否则返回 false。 - */ - public isReady (): boolean { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isReady(); - } - return false; - } - - /** - * @en Performs a network query the details of products available for sale in your app. - * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 - * @param productId @zh 产品ID。 @en product id. - * @param productType @zh 产品类型。 @en product type. - * - */ - public queryProductDetailsParams (productId: string[] | string, productType: ProductType): void { - if (productId instanceof Array) { - jsb.billing?.queryProductDetailsParams(productId, productType); - } else { - const productIdList: string[] = []; - productIdList.push(productId); - jsb.billing?.queryProductDetailsParams(productIdList, productType); - } - } - - /** - * @en Initiates the billing flow for an in-app purchase or subscription. - * @zh 启动应用内购买或订阅的计费流程。 - * @param productDetails @zh 产品详情。 @en product details. - * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. - */ - public launchBillingFlow (productDetails: native.ProductDetails[] | native.ProductDetails, selectedOfferToken: string | null): void { - if (productDetails instanceof Array) { - jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); - } else { - const productDetailsList: native.ProductDetails[] = []; - productDetailsList.push(productDetails); - jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); - } - } - - /** - * @en Consumes a given in-app product. - * @zh 消费指定的应用内产品。 - * @param purchase @zh 已经购买的产品。 @en Purchased Products. - */ - public consumePurchases (purchase: native.Purchase[] | native.Purchase): void { - if (purchase instanceof Array) { - jsb.billing?.consumePurchases(purchase); - } else { - const purchaseList: native.Purchase[] = []; - purchaseList.push(purchase); - jsb.billing?.consumePurchases(purchaseList); - } - } - - /** - * @en Acknowledges in-app purchases. - * @zh 确认应用内购买。 - * @param purchase @zh 已经购买的产品。 @en Purchased Products. - */ - public acknowledgePurchase (purchase: native.Purchase[] | native.Purchase): void { - if (purchase instanceof Array) { - jsb.billing?.acknowledgePurchase(purchase); - } else { - const purchaseList: native.Purchase[] = []; - purchaseList.push(purchase); - jsb.billing?.acknowledgePurchase(purchase); - } - } - - /** - * @en Returns purchases details for currently owned items bought within your app. - * @zh 返回您应用内当前拥有的购买商品的购买详情。 - * @param productType @zh 产品类型 @en Product type. - */ - public queryPurchasesAsync (productType: ProductType): void { - jsb.billing?.queryPurchasesAsync(productType); - } - - /** - * @en Gets the billing config, which stores configuration used to perform billing operations. - * @zh 获取计费配置,其中存储用于执行计费操作的配置。 - */ - public getBillingConfigAsync (): void { - jsb.billing?.getBillingConfigAsync(); - } - - /** - * @en Creates alternative billing only purchase details that can be used to report a transaction made - * via alternative billing without user choice to use Google Play billing. - * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 - */ - public createAlternativeBillingOnlyReportingDetailsAsync (): void { - jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); - } - - /** - * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. - * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 - */ - public isAlternativeBillingOnlyAvailableAsync (): void { - jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); - } - - /** - * @en Creates purchase details that can be used to report a transaction made via external offer. - * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 - */ - public createExternalOfferReportingDetailsAsync (): void { - jsb.billing?.createExternalOfferReportingDetailsAsync(); - } - - /** - * @en Checks the availability of providing external offer. - * @zh 检查提供外部报价的可用性。 - */ - public isExternalOfferAvailableAsync (): void { - jsb.billing?.isExternalOfferAvailableAsync(); - } - - /** - * @en Checks if the specified feature or capability is supported by the Play Store. - * @zh 检查 Play Store 是否支持指定的功能。 - * @param feature @zh 功能特性 @en feature. - */ - public isFeatureSupported (feature: string): native.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isFeatureSupported(feature); - } - return null; - } - - /** - * @en Shows the alternative billing only information dialog on top of the calling app. - * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 - */ - public showAlternativeBillingOnlyInformationDialog (): native.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showAlternativeBillingOnlyInformationDialog(); - } - return null; - } - - /** - * @en Shows the external offer information dialog on top of the calling app. - * @zh 在调用应用程序顶部显示外部优惠信息对话框。 - */ - public showExternalOfferInformationDialog (): native.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showExternalOfferInformationDialog(); - } - return null; - } - - /** - * @en Overlays billing related messages on top of the calling app. - * @zh 在调用应用程序上叠加与计费相关的消息。 - */ - public showInAppMessages (): native.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showInAppMessages(); - } - return null; - } - - public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { - this._eventTarget.on(type, callback, target); - return callback; - } - public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { - this._eventTarget.once(type, callback, target); - return callback; - } - public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { - this._eventTarget.off(eventType, callback, target); - } -} - -/** - * @en - * Interface for google play blling module. - * - * @zh - * google play blling模块的接口。 - * - */ -export const googlePlayBilling = new GooglePlayBilling(); diff --git a/cocos/core/platform/index.ts b/cocos/core/platform/index.ts index 15e21824c48..65b294d9aca 100644 --- a/cocos/core/platform/index.ts +++ b/cocos/core/platform/index.ts @@ -47,14 +47,3 @@ export { export { screen } from './screen'; export type { Screen } from './screen'; -export { - googlePlayBilling, - BillingEventType, - ProductType, - BillingResponseCode, - RecurrenceMode, - ConnectionState, - FeatureType, - PurchaseState, - InAppMessageResponseCode, -} from './billing/billing'; diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index c29f642decd..ec3b93cfc11 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -23,7 +23,6 @@ */ /* eslint-disable @typescript-eslint/no-namespace */ - import type { Color, Vec2 } from '../core'; export * from 'internal:native'; @@ -1436,7 +1435,9 @@ export declare namespace native { */ const adpf: { /** - * @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling. + * @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. + * The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates + * the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling. * @zh 提供设备在达到严重节流之前当前有多少热余量的估计值。值的范围是非负浮点数,其中0.0表示距离过热的固定距离,1.0表示设备将被严重限制,而大于1.0的值可能表示更重的限制。 * @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314 */ @@ -1476,359 +1477,4 @@ export declare namespace native { */ onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; } | undefined; - - /** - * @en Params containing the response code and the debug message from In-app Billing API response. - * @zh 包含应用内结算 API 响应代码和调试信息的参数 - */ - export interface BillingResult { - /** - * @en Debug message returned in In-app Billing API calls. - * @zh 应用内结算 API 调用中返回的调试消息。 - */ - readonly debugMessage: string; - /** - * @en Code returned in In-app Billing API calls. - * @zh 应用内结算 API 调用中返回的响应代码。 - */ - readonly responseCode: string; - readonly toStr: string; - } - - /** - * @en Represents the offer details to buy an one-time purchase product. - * @zh 代表一次性购买产品的报价详情。 - */ - export interface OneTimePurchaseOfferDetails { - /** - * @en The price for the payment in micro-units, where 1,000,000 micro-units equal one unit of the currency. - * @zh 以微单位返回支付价格,其中 1,000,000 个微单位等于 1 个货币单位。 - */ - readonly priceAmountMicros: number; - /** - * @en Formatted price for the payment, including its currency sign. - * @zh 支付的格式化价格,包括其货币单位。 - */ - readonly formattedPrice: string; - /** - * @en ISO 4217 currency code for price. - * @zh 价格的 ISO 4217 货币代码。 - */ - readonly priceCurrencyCode: string; - } - - /** - * @en Represents additional details of an installment subscription plan. - * @zh 表示分期付款订阅计划的附加详细信息。 - */ - export interface InstallmentPlanDetails { - /** - * @en Committed payments count after a user signs up for this subscription plan. - * @zh 用户注册此订阅计划后承诺的付款数量。 - */ - readonly installmentPlanCommitmentPaymentsCount: number; - /** - * @en Subsequent committed payments count after this subscription plan renews. - * @zh 此订阅计划续订后的后续承诺付款数量。 - */ - readonly subsequentInstallmentPlanCommitmentPaymentsCount: number; - } - - /** - * @en Represents a pricing phase, describing how a user pays at a point in time. - * @zh 表示定价阶段,描述用户在某个时间点如何付款。 - */ - export interface PricingPhase { - /** - * @en Number of cycles for which the billing period is applied. - * @zh 计费周期适用的周期数。 - */ - readonly billingCycleCount: number; - /** - * @en The price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency. - * @zh 微单位付款周期的价格,其中 1,000,000 个微单位等于 1 个货币单位。 - */ - readonly priceAmountMicros: number; - /** - * @en RecurrenceMode for the pricing phase. - * @zh 定价阶段的RecurrenceMode。 - */ - readonly recurrenceMode: number; - /** - * @en Billing period for which the given price applies, specified in ISO 8601 format. - * @zh 给定价格适用的计费期,以 ISO 8601 格式指定。 - */ - readonly billingPeriod: string; - /** - * @en Formatted price for the payment cycle, including its currency sign. - * @zh 付款周期的格式化价格,包括其货币符号。 - */ - readonly formattedPrice: string; - /** - * @en Returns ISO 4217 currency code for price. - * @zh 返回价格的 ISO 4217 货币代码。 - */ - readonly priceCurrencyCode: string; - } - - /** - * @en Represents the available purchase plans to buy a subscription product. - * @zh 代表一次性购买产品的报价详情。 - */ - export interface SubscriptionOfferDetails { - /** - * @en The base plan id associated with the subscription product. - * @zh 与订阅产品相关的基本计划 ID。 - */ - readonly basePlanId: string; - /** - * @en The offer id associated with the subscription product. - * @zh 与订阅产品相关的优惠 ID。 - */ - readonly offerId: string; - /** - * @en The offer tags associated with this Subscription Offer. - * @zh 与此订阅优惠相关的优惠标签。 - */ - readonly offerTags: string[]; - /** - * @en The offer token required to pass in launchBillingFlow to purchase the subscription product with these pricing phases. - * @zh 在 launchBillingFlow 中传递以使用这些定价阶段购买订阅产品所需的优惠令牌。 - */ - readonly offerToken: string; - /** - * @en The pricing phases for the subscription product. - * @zh 订阅产品的定价区间。 - */ - readonly pricingPhaseList: PricingPhase[]; - /** - * @en The additional details of an installment plan. - * @zh 分期付款计划的附加详细信息。 - */ - readonly installmentPlanDetails: InstallmentPlanDetails; - } - /** - * @en Represents the details of a one time or subscription product. - * @zh 代表一次性或订阅产品的详细信息。 - */ - export interface ProductDetails { - /** - * @en Hash code - * @zh hash值 - */ - readonly hashCode: number; - /** - * @en The description of the product. - * @zh 产品的描述。 - */ - readonly description: string; - /** - * @en The name of the product being sold. - * @zh 所售产品的名称。 - */ - readonly name: string; - /** - * @en The product's Id. - * @zh 产品的 ID。 - */ - readonly productId: string; - /** - * @en The ProductType of the product. - * @zh ProductType产品的。 - */ - readonly productType: string; - /** - * @en The title of the product being sold. - * @zh 所售产品的标题。 - */ - readonly title: string; - /** - * @en To string - * @zh 转换成字符串 - */ - readonly toStr: string; - /** - * @en The offer details of an one-time purchase product. - * @zh 代表一次性购买产品的报价详情。 - */ - readonly oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails; - /** - * @en A list containing all available offers to purchase a subscription product. - * @zh 返回包含购买订阅产品的所有可用优惠的列表。 - */ - readonly subscriptionOfferDetails: SubscriptionOfferDetails[]; - } - - /** - * @en Account identifiers that were specified when the purchase was made. - * @zh 购买时指定的帐户标识符。 - */ - export interface AccountIdentifiers { - /** - * @en The obfuscated account id specified in setObfuscatedAccountId. - * @zh 在setObfuscatedAccountId中设置的混淆账户id - */ - readonly obfuscatedAccountId: string; - /** - * @en The obfuscated profile id specified in setObfuscatedProfileId. - * @zh 在setObfuscatedProfileId中设置的混淆profile id - */ - readonly obfuscatedProfileId: string; - } - - /** - * @en Represents a pending change/update to the existing purchase. - * @zh 表示对现有购买的待定更改/更新。 - */ - export interface PendingPurchaseUpdate { - /** - * @en A token that uniquely identifies this pending transaction. - * @zh 唯一标识此待处理交易的令牌。 - */ - readonly purchaseToken: string; - /** - * @en The product ids. - * @zh 产品 ID。 - */ - readonly products: string[]; - } - - /** - * @en Represents an in-app billing purchase. - * @zh 代表应用内billing购买。 - */ - export interface Purchase { - /** - * @en One of PurchaseState indicating the state of the purchase. - * @zh PurchaseState表示购买状态的其中一个值。 - */ - readonly purchaseState: number; - /** - * @en The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970). - * @zh 产品购买的时间,以纪元(1970 年 1 月 1 日)以来的毫秒数表示。 - */ - readonly purchaseTime: number; - /** - * @en Indicates whether the purchase has been acknowledged. - * @zh 表示是否已确认购买。 - */ - readonly isAcknowledged: number; - /** - * @en Indicates whether the subscription renews automatically. - * @zh 指示订阅是否自动续订。 - */ - readonly isAutoRenewing: number; - /** - * @en Hash code - * @zh hash值 - */ - readonly hashCode: number; - - /** - * @en The quantity of the purchased product. - * @zh 购买产品的数量。 - */ - readonly quantity: number; - /** - * @en The payload specified when the purchase was acknowledged or consumed. - * @zh 确认或消费购买时指定的有效负载。 - */ - readonly developerPayload: string; - /** - * @en Returns a unique order identifier for the transaction. - * @zh 交易的唯一订单标识符。 - */ - readonly orderId: string; - /** - * @en Returns a String in JSON format that contains details about the purchase order. - * @zh 包含有关采购订单详细信息的 JSON 格式的字符串。 - */ - readonly originalJson: string; - /** - * @en The application package from which the purchase originated. - * @zh 购买来源的应用程序包。 - */ - readonly packageName: string; - /** - * @en A token that uniquely identifies a purchase for a given item and user pair. - * @zh 唯一标识给定商品和用户对的购买的令牌。 - */ - readonly purchaseToken: string; - /** - * @en String containing the signature of the purchase data that was signed with the private key of the developer. - * @zh 包含使用开发者私钥签名的购买数据签名的字符串。 - */ - readonly signature: string; - /** - * @en To string - * @zh 转换成字符串 - */ - readonly toStr: string; - - /** - * @en Returns account identifiers that were provided when the purchase was made. - * @zh 返回购买时提供的帐户标识符。 - */ - readonly accountIdentifiers: AccountIdentifiers; - /** - * @en The PendingPurchaseUpdate for an uncommitted transaction. - * @zh 返回PendingPurchaseUpdate未提交的事务。 - */ - readonly pendingPurchaseUpdate: PendingPurchaseUpdate; - /** - * @en the product Ids. - * @zh 产品 ID。 - */ - readonly products: string[]; - } - - export interface BillingConfig { - /** - * @en The customer's country code. - * @zh 客户的国家代码。 - */ - readonly countryCode: string; - } - - /** - * @en The details used to report transactions made via alternative billing without user choice to use Google Play billing. - * @zh 用于报告用户未选择使用 Google Play 结算方式而通过替代结算方式进行的交易的详细信息。 - */ - export interface AlternativeBillingOnlyReportingDetails { - /** - * @en An external transaction token that can be used to report a transaction made via alternative billing - * without user choice to use Google Play billing. - * @zh 返回一个外部交易令牌,该令牌可用于报告通过替代付款方式进行的交易,而无需用户选择使用 Google Play 付款方式。 - */ - readonly externalTransactionToken: string; - } - - /** - * @en The details used to report transactions made via external offer. - * @zh 用于报告通过外部报价进行的交易的详细信息。 - */ - export interface ExternalOfferReportingDetails { - /** - * @en An external transaction token that can be used to report a transaction made via external offer. - * @zh 可用于报告通过外部报价进行的交易的外部交易令牌。 - */ - readonly externalTransactionToken: string; - } - - /** - * @en Results related to in-app messaging. - * @zh 与应用程序内消息相关的结果。 - */ - export interface InAppMessageResult { - /** - * @en Response code for the in-app messaging API call. - * @zh 应用内消息传递 API 调用的响应代码。 - */ - readonly responseCode: number; - /** - * @en Token that identifies the purchase to be acknowledged, if any. - * @zh 返回标识需要确认的购买的令牌。 - */ - readonly purchaseToken: string; - } } diff --git a/editor/engine-features/render-config.json b/editor/engine-features/render-config.json index b03314b40c1..727b933afc3 100644 --- a/editor/engine-features/render-config.json +++ b/editor/engine-features/render-config.json @@ -312,6 +312,15 @@ "2d" ] }, + "vendor": { + "default": false, + "label": "i18n:ENGINE.features.vendor.label", + "description": "i18n:ENGINE.features.vendor.description", + "enginePlugin": false, + "isNativeModule": true, + "cmakeConfig": "USE_GOOGLE_PLAY_BILLING", + "hidden": true + }, "spine": { "default": true, "label": "i18n:ENGINE.features.spine.label", diff --git a/editor/engine-features/schema.json b/editor/engine-features/schema.json index 2d3b81a5a37..b27c1ae43c1 100644 --- a/editor/engine-features/schema.json +++ b/editor/engine-features/schema.json @@ -179,6 +179,9 @@ "tiled-map": { "$ref": "#/definitions/IFeatureItem" }, + "vendor": { + "$ref": "#/definitions/IFeatureItem" + }, "tween": { "$ref": "#/definitions/IFeatureItem" }, @@ -365,6 +368,9 @@ "tiled-map": { "$ref": "#/definitions/BaseItem" }, + "vendor": { + "$ref": "#/definitions/BaseItem" + }, "tween": { "$ref": "#/definitions/BaseItem" }, diff --git a/editor/i18n/en/localization.js b/editor/i18n/en/localization.js index 1b9f07c7e4c..aa848c57af5 100755 --- a/editor/i18n/en/localization.js +++ b/editor/i18n/en/localization.js @@ -1053,6 +1053,10 @@ module.exports = link(mixin({ label: "Tiled Map", description: "Tiled map support.", }, + vendor: { + label: "Vendor", + description: "Vendor support.", + }, spine: { label: "Spine Animation", description: "Spine Animation support.", diff --git a/editor/i18n/zh/localization.js b/editor/i18n/zh/localization.js index 9b68c2c0c19..cc2b94b8249 100755 --- a/editor/i18n/zh/localization.js +++ b/editor/i18n/zh/localization.js @@ -1031,6 +1031,10 @@ module.exports = link(mixin({ label: "Tiled 地图", description: "Tiled 地图支持。", }, + vendor: { + label: "Vendor", + description: "Vendor 支持.", + }, spine: { label: "Spine 动画", description: "Spine 动画支持。", diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index ce3695726c3..870e9714e60 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -47,7 +47,8 @@ cc_set_if_undefined(USE_GEOMETRY_RENDERER ON) cc_set_if_undefined(USE_WEBP ON) cc_set_if_undefined(NET_MODE 0) # 0 is client cc_set_if_undefined(USE_REMOTE_LOG OFF) - +cc_set_if_undefined(USE_ADPF OFF) +cc_set_if_undefined(USE_GOOGLE_PLAY_BILLING OFF) if(ANDROID AND NOT DEFINED USE_CCACHE) if("$ENV{COCOS_USE_CCACHE}" STREQUAL "1") @@ -565,8 +566,6 @@ elseif(ANDROID) cocos/platform/android/AndroidPlatform.cpp cocos/platform/android/AndroidPlatform.h cocos/platform/android/AndroidKeyCodes.cpp - cocos/platform/android/adpf_manager.h - cocos/platform/android/adpf_manager.cpp ) elseif(OPENHARMONY) cocos_source_files( @@ -601,6 +600,13 @@ elseif(IOS) ) endif() +if(USE_ADPF) + cocos_source_files( + cocos/platform/android/adpf_manager.h + cocos/platform/android/adpf_manager.cpp + ) +endif() + ############ platform : Abstract interface cocos_source_files( cocos/platform/interfaces/OSInterface.h @@ -754,6 +760,15 @@ elseif(ANDROID OR OHOS) endif() endif() +if(USE_GOOGLE_PLAY_BILLING) + cocos_source_files( + cocos/platform/android/modules/google_play/billing/billing.cpp + cocos/platform/android/modules/google_play/billing/billing.h + cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp + cocos/platform/android/modules/google_play/billing/JniBillingHelper.h + ) +endif() + if(ANDROID) cocos_source_files( cocos/platform/android/modules/Screen.cpp @@ -761,12 +776,7 @@ if(ANDROID) cocos/platform/android/modules/System.cpp cocos/platform/android/modules/System.h ) - cocos_source_files( - cocos/platform/android/modules/google_play/billing/billing.cpp - cocos/platform/android/modules/google_play/billing/billing.h - cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp - cocos/platform/android/modules/google_play/billing/JniBillingHelper.h - ) + elseif(OPENHARMONY) # Because there will be a definition of OHOS in harmonyos, it is necessary to define the macro of OPENHARMONY. elseif(OHOS) @@ -1121,7 +1131,6 @@ elseif(ANDROID) ${CWD}/cocos/platform/java/jni/JniImp.h ${CWD}/cocos/platform/java/jni/glue/MessagePipe.cpp ${CWD}/cocos/platform/java/jni/glue/MessagePipe.h - ${CWD}/cocos/platform/java/jni/JniCocosBilling.cpp ) cocos_source_files(MODULE ccfilesystem @@ -1185,6 +1194,10 @@ elseif(LINUX) ) endif() +if(USE_GOOGLE_PLAY_BILLING) + list(APPEND CC_JNI_SRC_FILES ${CWD}/cocos/platform/java/jni/JniCocosBilling.cpp) +endif() + ##### renderer cocos_source_files( cocos/renderer/core/PassUtils.h @@ -2566,8 +2579,6 @@ cocos_source_files( cocos/bindings/manual/jsb_xmlhttprequest.h cocos/bindings/manual/jsb_pipeline_manual.h cocos/bindings/manual/jsb_pipeline_manual.cpp - cocos/bindings/manual/jsb_adpf.cpp - cocos/bindings/manual/jsb_billing.cpp ) if(USE_AUDIO) cocos_source_files( @@ -2591,6 +2602,19 @@ if(USE_WEBSOCKET_SERVER) ) endif() +if(USE_ADPF) + cocos_source_files( + cocos/bindings/manual/jsb_adpf.cpp + ) +endif() + +if(USE_GOOGLE_PLAY_BILLING) + cocos_source_files( + cocos/bindings/manual/jsb_billing.cpp + ) + +endif() + if(ANDROID) cocos_source_files( cocos/bindings/manual/jsb_platform_android.cpp @@ -3122,6 +3146,8 @@ function(cc_apply_definations target) $<$:SCRIPT_ENGINE_TYPE=5> $<$:SCRIPT_ENGINE_TYPE=6> $<$,$>:CC_DEBUG=1> + $,CC_USE_APDF=1,CC_USE_USE_APDF=0> + $,CC_USE_GOOGLE_PLAY_BILLING=1,CC_USE_GOOGLE_PLAY_BILLING=0> ) endfunction() diff --git a/native/cocos/bindings/manual/jsb_billing.cpp b/native/cocos/bindings/manual/jsb_billing.cpp index 98e086c0633..6a772ccd39a 100644 --- a/native/cocos/bindings/manual/jsb_billing.cpp +++ b/native/cocos/bindings/manual/jsb_billing.cpp @@ -357,11 +357,11 @@ SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhases) static bool js_cc_PricingPhases_pricingPhaseList_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; - std::vector result; + cc::PricingPhases* result; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; - result = arg1->pricingPhaseList; + result = arg1->pricingPhases.get(); ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); SE_PRECONDITION2(ok, false, "Error processing arguments"); @@ -382,7 +382,7 @@ bool js_register_cc_PricingPhases(se::Object* obj) { // NOLINT cls->defineFinalizeFunction(_SE(js_delete_cc_PricingPhases)); cls->install(); - JSBClassType::registerClass(cls); + JSBClassType::registerClass(cls); se::ScriptEngine::getInstance()->clearException(); return true; @@ -478,11 +478,11 @@ SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerToken_get) static bool js_cc_SubscriptionOfferDetails_pricingPhases_get(se::State& s) { // NOLINT CC_UNUSED bool ok = true; cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; - std::vector result; + cc::PricingPhases* result; arg1 = SE_THIS_OBJECT(s); if (nullptr == arg1) return true; - result = arg1->pricingPhases; + result = arg1->pricingPhases.get(); ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); SE_PRECONDITION2(ok, false, "Error processing arguments"); diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index 1895e868e94..41685fb68eb 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -1630,7 +1630,9 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT jsb_register_TextEncoder(global); jsb_register_TextDecoder(global); +#if CC_USE_ADPF jsb_register_ADPF(__jsbObj); +#endif se::ScriptEngine::getInstance()->clearException(); diff --git a/native/cocos/bindings/manual/jsb_module_register.cpp b/native/cocos/bindings/manual/jsb_module_register.cpp index 9bdc6299b12..0074ff3ef80 100644 --- a/native/cocos/bindings/manual/jsb_module_register.cpp +++ b/native/cocos/bindings/manual/jsb_module_register.cpp @@ -192,7 +192,7 @@ bool jsb_register_all_modules() { se->addRegisterCallback(register_all_socketio); #endif -#if CC_PLATFORM == CC_PLATFORM_ANDROID +#if CC_USE_GOOGLE_PLAY_BILLING se->addRegisterCallback(jsb_register_all_billing); #endif diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java similarity index 100% rename from native/cocos/platform/android/java/src/com/cocos/lib/CocosBilling.java rename to native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java diff --git a/native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java similarity index 100% rename from native/cocos/platform/android/java/src/com/cocos/lib/CocosBillingHelper.java rename to native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 4987034344f..8dedbe4acac 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -17,10 +17,13 @@ android { sourceSets.main { aidl.srcDir "../java/src" - java.srcDir "../java/src" + java.srcDirs = ["../java/src"] manifest.srcFile "AndroidManifest.xml" } + if(project.hasProperty("PROP_ENABLE_BILLING") && project.PROP_ENABLE_BILLING.toBoolean()) { + sourceSets.main.java.srcDirs += ["../java/vendor"] + } buildDir = new File(rootProject.buildDir, project.name) buildTypes { @@ -37,5 +40,7 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') - implementation "com.android.billingclient:billing:7.1.0" + if(project.hasProperty("PROP_ENABLE_BILLING") && project.PROP_ENABLE_BILLING.toBoolean()) { + implementation "com.android.billingclient:billing:7.1.0" + } } diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp index 71859c32062..f4335097242 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp @@ -37,7 +37,7 @@ namespace { #ifndef JCLS_BILLING - #define JCLS_BILLING "com/cocos/lib/CocosBillingHelper" + #define JCLS_BILLING "com/cocos/billing/CocosBillingHelper" #endif }; // namespace @@ -112,6 +112,15 @@ template void callJSfunc(const char*, BillingResult*&&, AlternativeBillingOnlyRe template void callJSfunc(const char*, BillingResult*&&, ExternalOfferReportingDetails*&&); template void callJSfunc(const char*, InAppMessageResult*&&); +void JniBillingHelper::removeProductDetails(int productDetailsID) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "removeProductDetails", productDetailsID); +} + +void JniBillingHelper::removePurchase(int purchaseID) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "removePurchase", purchaseID); +} + + void JniBillingHelper::startConnection() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "startConnection"); } @@ -177,7 +186,7 @@ void JniBillingHelper::launchBillingFlow(const std::vector& pro jintArray result = env->NewIntArray(size); jint* buf = new jint[size]; for (int i = 0; i < size; ++i) { - buf[i] = productDetailsList[i]->hashCode; + buf[i] = productDetailsList[i]->_id; } env->SetIntArrayRegion(result, 0, size, buf); delete[] buf; @@ -196,7 +205,7 @@ void JniBillingHelper::consumePurchases(const std::vector& purchases) jintArray result = env->NewIntArray(size); jint* buf = new jint[size]; for (int i = 0; i < size; ++i) { - buf[i] = purchases[i]->hashCode; + buf[i] = purchases[i]->_id; } env->SetIntArrayRegion(result, 0, size, buf); delete[] buf; @@ -214,7 +223,7 @@ void JniBillingHelper::acknowledgePurchase(const std::vector& purchas jintArray result = env->NewIntArray(size); jint* buf = new jint[size]; for (int i = 0; i < size; ++i) { - buf[i] = purchases[i]->hashCode; + buf[i] = purchases[i]->_id; } env->SetIntArrayRegion(result, 0, size, buf); delete[] buf; @@ -254,18 +263,20 @@ void JniBillingHelper::onBillingServiceDisconnected(JNIEnv* env, jclass clazz) { void JniBillingHelper::onProductDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, - jobject productDetailsListObj) { + jobject productDetailsListObj, + jint startID) { auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); - std::vector productDetailsList = cc::JniBillingHelper::toProductDetailList(env, productDetailsListObj); + std::vector productDetailsList = cc::JniBillingHelper::toProductDetailList(env, productDetailsListObj, startID); cc::callJSfunc("onProductDetailsResponse", billingResult, productDetailsList); } void JniBillingHelper::onPurchasesUpdated(JNIEnv* env, jclass clazz, jobject billingResultObj, - jobject purchasesListObj) { + jobject purchasesListObj, + jint startID) { auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); if (purchasesListObj != nullptr) { - std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj); + std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj, startID); cc::callJSfunc("onPurchasesUpdated", billingResult, purchasesList); } else { cc::callJSfunc("onPurchasesUpdated", billingResult, std::vector()); @@ -277,9 +288,9 @@ void JniBillingHelper::onConsumeResponse(JNIEnv* env, jclass clazz, jobject bill cc::callJSfunc("onConsumeResponse", billingResult, cc::StringUtils::getStringUTFCharsJNI(env, static_cast(purchaseToken))); } -void JniBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesObj) { +void JniBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesListObj, jint startID) { auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); - std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesObj); + std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj, startID); cc::callJSfunc("onQueryPurchasesResponse", billingResult, purchasesList); } @@ -423,7 +434,7 @@ cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEn return details; } -std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj) { +std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj, jint startID) { jclass clazz = env->GetObjectClass(productListObj); jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productListObj, "size"); @@ -431,6 +442,7 @@ std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, for (int i = 0; i < size; ++i) { jobject productDetailObj = env->CallObjectMethod(productListObj, listGetMethod, i); cc::ProductDetails* productDetails = cc::JniBillingHelper::toProductDetail(env, productDetailObj); + productDetails->_id = startID++; productDetailsList.push_back(productDetails); } return std::move(productDetailsList); @@ -495,7 +507,7 @@ cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env return pendingPurchaseUpdate; } -std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj) { +std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj, jint startID) { jclass clazz = env->GetObjectClass(productsListObj); jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productsListObj, "size"); @@ -503,6 +515,7 @@ std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject pro for (int i = 0; i < size; ++i) { jobject purchaseObj = env->CallObjectMethod(productsListObj, listGetMethod, i); cc::Purchase* purchase = cc::JniBillingHelper::toPurchase(env, purchaseObj); + purchase->_id = startID++; purchases.push_back(purchase); } return std::move(purchases); diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h index 90c11da7a4c..33df0534b29 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h +++ b/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h @@ -48,6 +48,8 @@ class InAppMessageResult; class CC_DLL JniBillingHelper { public: + static void removeProductDetails(int productDetailsID); + static void removePurchase(int purchaseID); static void startConnection(); static void endConnection(); static int getConnectionState(); @@ -74,12 +76,14 @@ class CC_DLL JniBillingHelper { static void onBillingServiceDisconnected(JNIEnv *env, jclass clazz); static void onProductDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject productDetailsListObj); + jobject productDetailsListObj, + jint startID); static void onPurchasesUpdated(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject purchaseListObj); + jobject purchaseListObj, + jint startID); static void onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken); - static void onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject purchaseListObj); + static void onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject purchaseListObj, jint startID); static void onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); static void onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj); static void onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj); @@ -92,8 +96,8 @@ class CC_DLL JniBillingHelper { private: static BillingResult *toBillingResult(JNIEnv *env, jobject obj); - static std::vector toProductDetailList(JNIEnv *env, jobject productsObj); - static std::vector toPurchaseList(JNIEnv *env, jobject productsObj); + static std::vector toProductDetailList(JNIEnv *env, jobject productsObj, jint startID); + static std::vector toPurchaseList(JNIEnv *env, jobject productsObj, jint startID); static BillingConfig *toBillingConfig(JNIEnv *env, jobject billingConfigObj); static AlternativeBillingOnlyReportingDetails *toAlternativeBillingOnlyReportingDetails(JNIEnv *env, jobject alternativeBillingOnlyReportingDetailsObj); static ExternalOfferReportingDetails *toExternalOfferReportingDetails(JNIEnv *env, jobject externalOfferReportingDetailsObj); diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.cpp b/native/cocos/platform/android/modules/google_play/billing/billing.cpp index 3fe72a3854c..a5f00640d38 100644 --- a/native/cocos/platform/android/modules/google_play/billing/billing.cpp +++ b/native/cocos/platform/android/modules/google_play/billing/billing.cpp @@ -29,6 +29,18 @@ namespace cc { +ProductDetails::~ProductDetails() { + for (auto* ptr : subscriptionOfferDetails) { + delete ptr; + } + subscriptionOfferDetails.clear(); + JniBillingHelper::removeProductDetails(_id); +} + +Purchase::~Purchase() { + JniBillingHelper::removePurchase(_id); +} + void Billing::startConnection() { JniBillingHelper::startConnection(); } diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.h b/native/cocos/platform/android/modules/google_play/billing/billing.h index 022ab816956..9445c60e607 100644 --- a/native/cocos/platform/android/modules/google_play/billing/billing.h +++ b/native/cocos/platform/android/modules/google_play/billing/billing.h @@ -84,15 +84,11 @@ class CC_DLL SubscriptionOfferDetails { class CC_DLL ProductDetails : public cc::RefCounted { public: - ~ProductDetails() { - for (auto* ptr : subscriptionOfferDetails) { - delete ptr; - } - subscriptionOfferDetails.clear(); - } + ~ProductDetails() override; bool equals(const ProductDetails& other) const { return hashCode == other.hashCode; } + int _id; // This is an ID that is not visible to ts and is used to free the java object. int hashCode; std::string description; std::string name; @@ -119,10 +115,11 @@ class CC_DLL PendingPurchaseUpdate { class CC_DLL Purchase : public cc::RefCounted { public: + ~Purchase() override; bool equals(const Purchase& other) const { return hashCode == other.hashCode; } - + int _id; // This is an ID that is not visible to ts and is used to free the java object. bool isAcknowledged; bool isAutoRenewing; int purchaseState; diff --git a/native/cocos/platform/java/jni/JniCocosBilling.cpp b/native/cocos/platform/java/jni/JniCocosBilling.cpp index 37e03deef1d..5d76cf3ee9b 100644 --- a/native/cocos/platform/java/jni/JniCocosBilling.cpp +++ b/native/cocos/platform/java/jni/JniCocosBilling.cpp @@ -29,82 +29,84 @@ extern "C" { // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onBillingSetupFinished(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { cc::JniBillingHelper::onBillingServiceDisconnected(env, clazz); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject productDetailsListObj) { - cc::JniBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj); + jobject productDetailsListObj, + jint startID) { + cc::JniBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj, startID); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject purchaseListObj) { - cc::JniBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj); + jobject purchaseListObj, + jint startID) { + cc::JniBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj, startID); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { cc::JniBillingHelper::onConsumeResponse(env, clazz, billingResultObj, purchaseToken); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject purchaseListObj) { - cc::JniBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj); +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, + jobject purchaseListObj, jint startID) { + cc::JniBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj, startID); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onAcknowledgePurchaseResponse(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { cc::JniBillingHelper::onBillingConfigResponse(env, clazz, billingResultObj, billingConfigObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { cc::JniBillingHelper::onAlternativeBillingOnlyTokenResponse(env, clazz, billingResultObj, alternativeBillingOnlyReportingDetailsObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { cc::JniBillingHelper::onExternalOfferReportingDetailsResponse(env, clazz, billingResultObj, externalOfferReportingDetailsObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onExternalOfferAvailabilityResponse(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { cc::JniBillingHelper::onExternalOfferInformationDialogResponse(env, clazz, billingResultObj); } // NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_lib_CocosBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { +JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { cc::JniBillingHelper::onInAppMessageResponse(env, clazz, inAppMessageResultObj); } } diff --git a/scripts/native-pack-tool/source/base/manager.ts b/scripts/native-pack-tool/source/base/manager.ts index f325cb8f11f..0a43e3c2439 100644 --- a/scripts/native-pack-tool/source/base/manager.ts +++ b/scripts/native-pack-tool/source/base/manager.ts @@ -2,7 +2,7 @@ import { Paths } from "../utils"; import { CocosParams, NativePackTool } from "./default"; -export type ISupportPlatform = 'mac-os' | 'mac' | 'ios' | 'android' | 'ohos'; +export type ISupportPlatform = 'mac-os' | 'mac' | 'ios' | 'android' | 'google-play' | 'ohos'; export class NativePackToolManager { static Paths: Paths; diff --git a/scripts/native-pack-tool/source/index.ts b/scripts/native-pack-tool/source/index.ts index 6d4e2653656..4edb4724737 100644 --- a/scripts/native-pack-tool/source/index.ts +++ b/scripts/native-pack-tool/source/index.ts @@ -1,8 +1,8 @@ - import { nativePackToolMg } from './base/manager'; import { MacPackTool } from './platforms/mac'; import { WindowsPackTool } from './platforms/windows'; import { AndroidPackTool } from './platforms/android'; +import { GooglePlayPackTool } from './platforms/google-play'; import { OpenHarmonyPackTool } from './platforms/openharmony'; import { OHOSPackTool } from './platforms/ohos'; import { IOSPackTool } from './platforms/ios'; @@ -12,6 +12,7 @@ nativePackToolMg.register('ios', new IOSPackTool()); nativePackToolMg.register('mac', new MacPackTool()); nativePackToolMg.register('windows', new WindowsPackTool()); nativePackToolMg.register('android', new AndroidPackTool()); +nativePackToolMg.register('google-play', new GooglePlayPackTool()); nativePackToolMg.register('openharmony', new OpenHarmonyPackTool()); nativePackToolMg.register('ohos', new OHOSPackTool()); nativePackToolMg.register('huawei-agc', new HuaweiAGCPackTool()); diff --git a/templates/google-play/build/.idea/encodings.xml b/templates/google-play/build/.idea/encodings.xml new file mode 100644 index 00000000000..c2bae49d78c --- /dev/null +++ b/templates/google-play/build/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/templates/google-play/build/build.gradle b/templates/google-play/build/build.gradle new file mode 100644 index 00000000000..930c4fa516a --- /dev/null +++ b/templates/google-play/build/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: NATIVE_DIR +"/build.gradle" diff --git a/templates/google-play/build/gradle.properties b/templates/google-play/build/gradle.properties new file mode 100644 index 00000000000..ba524fd48e9 --- /dev/null +++ b/templates/google-play/build/gradle.properties @@ -0,0 +1,77 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +org.gradle.jvmargs=-Xmx4608m -Dfile.encoding=UTF-8 +android.useAndroidX=true + +android.injected.testOnly=false + +android.native.buildOutput=verbose + +# Android SDK version that will be used as the compile project +PROP_COMPILE_SDK_VERSION=34 + +# Android SDK version that will be used as the earliest version of android this application can run on +PROP_MIN_SDK_VERSION=21 + +# Android SDK version that will be used as the latest version of android this application has been tested on +PROP_TARGET_SDK_VERSION=34 + +# Android Build Tools version that will be used as the compile project +PROP_BUILD_TOOLS_VERSION=34.0.0 + +# Android Application Name +PROP_APP_NAME=Cocos Game + +# Instant App +PROP_ENABLE_INSTANT_APP=true + +# InputSDK +PROP_ENABLE_INPUTSDK=false + +PROP_NDK_PATH= + +# Build variant +PROP_IS_DEBUG=true + +# Cocos Engine Path +COCOS_ENGINE_PATH= + +# Billing +PROP_ENABLE_BILLING=true + +# Res path +RES_PATH= + +# Native source dir +NATIVE_DIR= + +# Application ID +APPLICATION_ID= + +# List of CPU Archtexture to build that application with +# Available architextures (armeabi-v7a | arm64-v8a | x86 | x86_64) +# To build for multiple architexture, use the `:` between them +# Example - PROP_APP_ABI=armeabi-v7a:arm64-v8a:x86 +PROP_APP_ABI=arm64-v8a + +# fill in sign information for release mode +# RELEASE_STORE_FILE= +# RELEASE_STORE_PASSWORD= +# RELEASE_KEY_ALIAS= +# RELEASE_KEY_PASSWORD= diff --git a/templates/google-play/build/gradle/wrapper/gradle-wrapper.jar b/templates/google-play/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/templates/google-play/build/gradlew.bat b/templates/google-play/build/gradlew.bat new file mode 100644 index 00000000000..8a0b282aa68 --- /dev/null +++ b/templates/google-play/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templates/google-play/build/libservice/AndroidManifest.xml b/templates/google-play/build/libservice/AndroidManifest.xml new file mode 100644 index 00000000000..0a0938ae37e --- /dev/null +++ b/templates/google-play/build/libservice/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/templates/google-play/build/libservice/build.gradle b/templates/google-play/build/libservice/build.gradle new file mode 100644 index 00000000000..d8228188f7c --- /dev/null +++ b/templates/google-play/build/libservice/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + namespace 'com.cocos.service' + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + } + + sourceSets.main { + java.srcDirs "src" + res.srcDirs 'res' + jniLibs.srcDirs 'libs' + manifest.srcFile "AndroidManifest.xml" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation project(':libcocos') +} diff --git a/templates/google-play/build/libservice/proguard-rules.pro b/templates/google-play/build/libservice/proguard-rules.pro new file mode 100644 index 00000000000..f1b424510da --- /dev/null +++ b/templates/google-play/build/libservice/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java b/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java new file mode 100644 index 00000000000..e7ab46f77da --- /dev/null +++ b/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java @@ -0,0 +1,185 @@ +/**************************************************************************** + Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd. + http://www.cocos.com + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.service; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.os.Bundle; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public final class SDKWrapper { + private SDKWrapper() {} + private static class SDKWrapperInstance { + private static final SDKWrapper mInstance = new SDKWrapper(); + } + public static SDKWrapper shared() { return SDKWrapperInstance.mInstance; } + + @SuppressWarnings("unused") + public interface SDKInterface { + default void init(Context context) {} + default void onStart() {} + default void onPause() {} + default void onResume() {} + default void onStop() {} + default void onDestroy() {} + default void onRestart() {} + default void onNewIntent(Intent intent) {} + default void onActivityResult(int requestCode, int resultCode, Intent data) {} + default void onConfigurationChanged(Configuration newConfig) {} + default void onRestoreInstanceState(Bundle savedInstanceState) {} + default void onSaveInstanceState(Bundle outState) {} + default void onBackPressed() {} + default void onLowMemory() {} + } + + private WeakReference mActivity = null; + private List serviceInstances; + + private void loadSDKInterface() { + ArrayList instances = new ArrayList<>(); + try { + String json = this.getJson("service.json"); + JSONObject jsonObject = new JSONObject(json); + JSONArray serviceClasses = jsonObject.getJSONArray("serviceClasses"); + if (serviceClasses == null) return; + int length = serviceClasses.length(); + for (int i = 0; i < length; i++) { + instances.add((SDKInterface) Class.forName(serviceClasses.getString(i)).newInstance()); + } + } catch (Exception ignored) { } + this.serviceInstances = instances; + } + + @SuppressWarnings("SameParameterValue") + private String getJson(String fileName) { + StringBuilder sb = new StringBuilder(); + try { + AssetManager am = this.mActivity.get().getAssets(); + BufferedReader br = new BufferedReader(new InputStreamReader(am.open(fileName))); + String next; + while (null != (next = br.readLine())) { sb.append(next); } + } catch (IOException e) { + sb.delete(0, sb.length()); + } + return sb.toString().trim(); + } + + public Activity getActivity() { return this.mActivity.get(); } + + public void init(Activity activity) { + this.mActivity = new WeakReference<>(activity); + this.loadSDKInterface(); + for (SDKInterface sdk : this.serviceInstances) { + sdk.init(activity); + } + } + + public void onResume() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onResume(); + } + } + + public void onPause() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onPause(); + } + } + + public void onDestroy() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onDestroy(); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onActivityResult(requestCode, resultCode, data); + } + } + + public void onNewIntent(Intent intent) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onNewIntent(intent); + } + } + + public void onRestart() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onRestart(); + } + } + + public void onStop() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onStop(); + } + } + + public void onBackPressed() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onBackPressed(); + } + } + + public void onConfigurationChanged(Configuration newConfig) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onConfigurationChanged(newConfig); + } + } + + public void onRestoreInstanceState(Bundle savedInstanceState) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onRestoreInstanceState(savedInstanceState); + } + } + + public void onSaveInstanceState(Bundle outState) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onSaveInstanceState(outState); + } + } + + public void onStart() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onStart(); + } + } + + public void onLowMemory() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onLowMemory(); + } + } +} \ No newline at end of file diff --git a/templates/google-play/build/settings.gradle b/templates/google-play/build/settings.gradle new file mode 100644 index 00000000000..a92f255a760 --- /dev/null +++ b/templates/google-play/build/settings.gradle @@ -0,0 +1,10 @@ +include ':libcocos',':libservice',':app' +project(':libcocos').projectDir = new File(COCOS_ENGINE_PATH,'cocos/platform/android/libcocos2dx') +project(':app').projectDir = new File(NATIVE_DIR, 'app') +project(':app').name = "CocosGame" +if(PROP_ENABLE_INSTANT_APP == "true" || PROP_ENABLE_INSTANT_APP == "yes") { + include ':instantapp' + project(':instantapp').projectDir = new File(NATIVE_DIR, 'instantapp') +} + +rootProject.name = "CocosGame" diff --git a/templates/google-play/template/.gitignore b/templates/google-play/template/.gitignore new file mode 100644 index 00000000000..9c4de5825b1 --- /dev/null +++ b/templates/google-play/template/.gitignore @@ -0,0 +1,7 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/templates/google-play/template/CMakeLists.txt b/templates/google-play/template/CMakeLists.txt new file mode 100644 index 00000000000..b6f90857a94 --- /dev/null +++ b/templates/google-play/template/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.8) + +option(APP_NAME "Project Name" "CocosGame") +project(${APP_NAME} CXX) +set(CC_LIB_NAME cocos) +set(CC_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}) +set(CC_PROJ_SOURCES) +set(CC_COMMON_SOURCES) +set(CC_ALL_SOURCES) + +include(${CMAKE_CURRENT_LIST_DIR}/../common/CMakeLists.txt) + +cc_android_before_target(${CC_LIB_NAME}) +add_library(${CC_LIB_NAME} SHARED ${CC_ALL_SOURCES}) +cc_android_after_target(${CC_LIB_NAME}) diff --git a/templates/google-play/template/app/.gitignore b/templates/google-play/template/app/.gitignore new file mode 100644 index 00000000000..1de99493d64 --- /dev/null +++ b/templates/google-play/template/app/.gitignore @@ -0,0 +1,2 @@ +/build +/jniLibs diff --git a/templates/google-play/template/app/AndroidManifest.xml b/templates/google-play/template/app/AndroidManifest.xml new file mode 100644 index 00000000000..a85bb7ca777 --- /dev/null +++ b/templates/google-play/template/app/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/google-play/template/app/build.gradle b/templates/google-play/template/app/build.gradle new file mode 100644 index 00000000000..184a68947f0 --- /dev/null +++ b/templates/google-play/template/app/build.gradle @@ -0,0 +1,113 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +apply plugin: 'com.android.application' + +RES_PATH = RES_PATH.replace("\\", "/") +COCOS_ENGINE_PATH = COCOS_ENGINE_PATH.replace("\\", "/") + +buildDir = "${RES_PATH}/proj/build/${project.name ==~ /^[_a-zA-Z0-9-]+$/ ? project.name : 'CocosGame'}" +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + buildToolsVersion PROP_BUILD_TOOLS_VERSION + ndkPath PROP_NDK_PATH + namespace APPLICATION_ID + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId APPLICATION_ID + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + targets "cocos" + arguments "-DRES_DIR=${RES_PATH}", "-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE" + } + ndk { abiFilters PROP_APP_ABI.split(':') } + } + } + + sourceSets.main { + java.srcDirs "../src", "src" + res.srcDirs "../res", 'res', "${RES_PATH}/proj/res" + jniLibs.srcDirs "../libs", 'libs' + manifest.srcFile "AndroidManifest.xml" + assets.srcDir "${RES_PATH}/data" + jniLibs { + // Vulkan validation layer + // srcDir "${android.ndkDirectory}/sources/third_party/vulkan/src/build-android/jniLibs" + } + } + + externalNativeBuild { + cmake { + version "3.22.1" + path "../CMakeLists.txt" + buildStagingDirectory "${RES_PATH}/proj/build" + } + } + + signingConfigs { + + release { + if (project.hasProperty("RELEASE_STORE_FILE") && !RELEASE_STORE_FILE.isEmpty()) { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + } + } + + buildTypes { + release { + debuggable false + jniDebuggable false + renderscriptDebuggable false + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (project.hasProperty("RELEASE_STORE_FILE")) { + signingConfig signingConfigs.release + } + + externalNativeBuild { + cmake { + // switch HIDE_SYMBOLS to OFF to skip compilation flag `-fvisibility=hidden` + arguments "-DHIDE_SYMBOLS=ON" + } + } + + if (!Boolean.parseBoolean(PROP_IS_DEBUG)) { + getIsDefault().set(true) + } + + } + + debug { + debuggable true + jniDebuggable true + renderscriptDebuggable true + // resValue "string", "app_name", "${PROP_APP_NAME}-dbg" + // applicationIdSuffix ".debug" + } + } +} + +dependencies { + implementation fileTree(dir: '../libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: "${COCOS_ENGINE_PATH}/cocos/platform/android/java/libs", include: ['*.jar']) + implementation project(':libservice') + implementation project(':libcocos') + if (Boolean.parseBoolean(PROP_ENABLE_INPUTSDK)) { + implementation 'com.google.android.libraries.play.games:inputmapping:1.1.0-beta' + implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" + } +} diff --git a/templates/google-play/template/app/proguard-rules.pro b/templates/google-play/template/app/proguard-rules.pro new file mode 100644 index 00000000000..fc884b76762 --- /dev/null +++ b/templates/google-play/template/app/proguard-rules.pro @@ -0,0 +1,54 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Proguard Cocos2d-x-lite for release +-keep public class com.cocos.** { *; } +-dontwarn com.cocos.** + +# Proguard Apache HTTP for release +-keep class org.apache.http.** { *; } +-dontwarn org.apache.http.** + +# Proguard okhttp for release +-keep class okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +# Proguard Android Webivew for release. you can comment if you are not using a webview +-keep public class android.net.http.SslError +-keep public class android.webkit.WebViewClient + +-keep public class com.google.** { *; } + +-dontwarn android.webkit.WebView +-dontwarn android.net.http.SslError +-dontwarn android.webkit.WebViewClient + +# This is generated automatically by the Android Gradle plugin. +-dontwarn android.hardware.BatteryState +-dontwarn android.hardware.lights.Light +-dontwarn android.hardware.lights.LightState$Builder +-dontwarn android.hardware.lights.LightState +-dontwarn android.hardware.lights.LightsManager$LightsSession +-dontwarn android.hardware.lights.LightsManager +-dontwarn android.hardware.lights.LightsRequest$Builder +-dontwarn android.hardware.lights.LightsRequest +-dontwarn android.net.ssl.SSLSockets +-dontwarn android.os.VibratorManager \ No newline at end of file diff --git a/templates/google-play/template/app/src/com/cocos/game/AppActivity.java b/templates/google-play/template/app/src/com/cocos/game/AppActivity.java new file mode 100644 index 00000000000..5aaece88515 --- /dev/null +++ b/templates/google-play/template/app/src/com/cocos/game/AppActivity.java @@ -0,0 +1,125 @@ +/**************************************************************************** +Copyright (c) 2015-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +package com.cocos.game; + +import android.os.Bundle; +import android.content.Intent; +import android.content.res.Configuration; + +import com.cocos.service.SDKWrapper; +import com.cocos.lib.CocosActivity; + +public class AppActivity extends CocosActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // DO OTHER INITIALIZATION BELOW + SDKWrapper.shared().init(this); + + } + + @Override + protected void onResume() { + super.onResume(); + SDKWrapper.shared().onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + SDKWrapper.shared().onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508 + if (!isTaskRoot()) { + return; + } + SDKWrapper.shared().onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + SDKWrapper.shared().onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + SDKWrapper.shared().onNewIntent(intent); + } + + @Override + protected void onRestart() { + super.onRestart(); + SDKWrapper.shared().onRestart(); + } + + @Override + protected void onStop() { + super.onStop(); + SDKWrapper.shared().onStop(); + } + + @Override + public void onBackPressed() { + SDKWrapper.shared().onBackPressed(); + super.onBackPressed(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + SDKWrapper.shared().onConfigurationChanged(newConfig); + super.onConfigurationChanged(newConfig); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + SDKWrapper.shared().onRestoreInstanceState(savedInstanceState); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + SDKWrapper.shared().onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + @Override + protected void onStart() { + SDKWrapper.shared().onStart(); + super.onStart(); + } + + @Override + public void onLowMemory() { + SDKWrapper.shared().onLowMemory(); + super.onLowMemory(); + } +} diff --git a/templates/google-play/template/build-cfg.json b/templates/google-play/template/build-cfg.json new file mode 100644 index 00000000000..fb658e29853 --- /dev/null +++ b/templates/google-play/template/build-cfg.json @@ -0,0 +1,8 @@ +{ + "ndk_module_path" :[ + "${COCOS_ROOT}", + "${COCOS_ROOT}/cocos", + "${COCOS_ROOT}/external" + ], + "copy_resources": [] +} diff --git a/templates/google-play/template/build.gradle b/templates/google-play/template/build.gradle new file mode 100644 index 00000000000..84203e08ee1 --- /dev/null +++ b/templates/google-play/template/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/templates/google-play/template/instantapp/AndroidManifest.xml b/templates/google-play/template/instantapp/AndroidManifest.xml new file mode 100644 index 00000000000..0216ac9da8f --- /dev/null +++ b/templates/google-play/template/instantapp/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/google-play/template/instantapp/build.gradle b/templates/google-play/template/instantapp/build.gradle new file mode 100644 index 00000000000..4336667f2c4 --- /dev/null +++ b/templates/google-play/template/instantapp/build.gradle @@ -0,0 +1,100 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +apply plugin: 'com.android.application' + +RES_PATH = RES_PATH.replace("\\", "/") +COCOS_ENGINE_PATH = COCOS_ENGINE_PATH.replace("\\", "/") +buildDir = "${RES_PATH}/proj/build/instantapp" + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + buildToolsVersion PROP_BUILD_TOOLS_VERSION + ndkPath PROP_NDK_PATH + namespace APPLICATION_ID + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + targets "cocos" + arguments "-DRES_DIR=${RES_PATH}", "-DCOCOS_X_PATH=${COCOS_ENGINE_PATH}","-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE", "-DANDROID_LD=gold" + cppFlags "-frtti -fexceptions -fsigned-char -DANDROID_INSTANT=1" + } + ndk { abiFilters PROP_APP_ABI.split(':') } + } + } + + sourceSets.main { + java.srcDirs "../src", "src" + res.srcDirs "../res", 'res', "${RES_PATH}/proj/res" + jniLibs.srcDirs "../libs", 'libs' + manifest.srcFile "AndroidManifest.xml" + assets.srcDir "${RES_PATH}/data" + } + + externalNativeBuild { + cmake { + version "3.22.1" + path "../CMakeLists.txt" + buildStagingDirectory "${RES_PATH}/proj/build" + } + } + + signingConfigs { + + release { + if (project.hasProperty("RELEASE_STORE_FILE") && !RELEASE_STORE_FILE.isEmpty()) { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + } + } + + buildTypes { + release { + debuggable false + jniDebuggable false + renderscriptDebuggable false + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (project.hasProperty("RELEASE_STORE_FILE")) { + signingConfig signingConfigs.release + } + + externalNativeBuild { + cmake { + // switch HIDE_SYMBOLS to OFF to skip compilation flag `-fvisibility=hidden` + arguments "-DHIDE_SYMBOLS=ON" + } + } + } + + debug { + debuggable true + jniDebuggable true + renderscriptDebuggable true + // resValue "string", "app_name", "${PROP_APP_NAME}-dbg" + // applicationIdSuffix ".debug" + } + } +} + +dependencies { + implementation fileTree(dir: '../libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: "${COCOS_ENGINE_PATH}/cocos/platform/android/java/libs", include: ['*.jar']) + implementation project(':libservice') + implementation project(':libcocos') +} diff --git a/templates/google-play/template/instantapp/proguard-rules.pro b/templates/google-play/template/instantapp/proguard-rules.pro new file mode 100644 index 00000000000..fc884b76762 --- /dev/null +++ b/templates/google-play/template/instantapp/proguard-rules.pro @@ -0,0 +1,54 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Proguard Cocos2d-x-lite for release +-keep public class com.cocos.** { *; } +-dontwarn com.cocos.** + +# Proguard Apache HTTP for release +-keep class org.apache.http.** { *; } +-dontwarn org.apache.http.** + +# Proguard okhttp for release +-keep class okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +# Proguard Android Webivew for release. you can comment if you are not using a webview +-keep public class android.net.http.SslError +-keep public class android.webkit.WebViewClient + +-keep public class com.google.** { *; } + +-dontwarn android.webkit.WebView +-dontwarn android.net.http.SslError +-dontwarn android.webkit.WebViewClient + +# This is generated automatically by the Android Gradle plugin. +-dontwarn android.hardware.BatteryState +-dontwarn android.hardware.lights.Light +-dontwarn android.hardware.lights.LightState$Builder +-dontwarn android.hardware.lights.LightState +-dontwarn android.hardware.lights.LightsManager$LightsSession +-dontwarn android.hardware.lights.LightsManager +-dontwarn android.hardware.lights.LightsRequest$Builder +-dontwarn android.hardware.lights.LightsRequest +-dontwarn android.net.ssl.SSLSockets +-dontwarn android.os.VibratorManager \ No newline at end of file diff --git a/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java b/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java new file mode 100644 index 00000000000..318e8398d31 --- /dev/null +++ b/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java @@ -0,0 +1,125 @@ +/**************************************************************************** +Copyright (c) 2015-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +package com.cocos.game; + +import android.os.Bundle; +import android.content.Intent; +import android.content.res.Configuration; + +import com.cocos.service.SDKWrapper; +import com.cocos.lib.CocosActivity; + +public class InstantActivity extends CocosActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // DO OTHER INITIALIZATION BELOW + SDKWrapper.shared().init(this); + + } + + @Override + protected void onResume() { + super.onResume(); + SDKWrapper.shared().onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + SDKWrapper.shared().onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508 + if (!isTaskRoot()) { + return; + } + SDKWrapper.shared().onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + SDKWrapper.shared().onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + SDKWrapper.shared().onNewIntent(intent); + } + + @Override + protected void onRestart() { + super.onRestart(); + SDKWrapper.shared().onRestart(); + } + + @Override + protected void onStop() { + super.onStop(); + SDKWrapper.shared().onStop(); + } + + @Override + public void onBackPressed() { + SDKWrapper.shared().onBackPressed(); + super.onBackPressed(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + SDKWrapper.shared().onConfigurationChanged(newConfig); + super.onConfigurationChanged(newConfig); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + SDKWrapper.shared().onRestoreInstanceState(savedInstanceState); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + SDKWrapper.shared().onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + @Override + protected void onStart() { + SDKWrapper.shared().onStart(); + super.onStart(); + } + + @Override + public void onLowMemory() { + SDKWrapper.shared().onLowMemory(); + super.onLowMemory(); + } +} diff --git a/templates/google-play/template/res/mipmap-hdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..888c0d56abef8d4ca9123954777b6f820c967b6f GIT binary patch literal 6780 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 literal 0 HcmV?d00001 diff --git a/templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1e5a6b3776489f893d8ed4768ae9fe356b4df8 GIT binary patch literal 16236 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‹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 literal 0 HcmV?d00001 diff --git a/templates/google-play/template/res/values/strings.xml b/templates/google-play/template/res/values/strings.xml new file mode 100644 index 00000000000..8542005550c --- /dev/null +++ b/templates/google-play/template/res/values/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/tsconfig.json b/tsconfig.json index 273a16fd24f..369cb10f46b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -55,6 +55,7 @@ "exports/**/*.ts", "editor/exports/**/*.ts", "typedoc-index.ts", - "pal/**/*.ts" + "pal/**/*.ts", + "vendor/**/*.ts" ] } diff --git a/typedoc-index.ts b/typedoc-index.ts index f5f38d1b343..2e0cd205648 100644 --- a/typedoc-index.ts +++ b/typedoc-index.ts @@ -29,3 +29,4 @@ export * from './exports/video'; export * from './exports/webview'; export * from './exports/sorting'; export * from './exports/xr'; +export * from './exports/vendor'; diff --git a/vender/google/play/billing/billing.ts b/vender/google/play/billing/billing.ts new file mode 100644 index 00000000000..7006dd9eb7f --- /dev/null +++ b/vender/google/play/billing/billing.ts @@ -0,0 +1,851 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ +import { JSB } from 'internal:constants'; +import { native } from '../../../../cocos/native-binding'; +import { EventTarget } from '../../../../cocos/core/event'; + +//declare const jsb: any; + +export namespace googleplay { + /** + * @en Google Play Billing event type + * @zh Google Play Billing事件类型 + */ + export enum BillingEventType { + /** + * @en + * Called to notify that setup is complete. + * + * @zh + * 当安装已经完成时触发。 + */ + BILLING_SETUP_FINISHED = 'billing_setup_finished', + /** + * @en + * Called to notify that the connection to the billing service was lost. + * + * @zh + * 当Billing服务连接断开时触发。 + */ + BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', + /** + * @en + * Listen to this event to get notifications of purchase updates. + * + * @zh + * 监听这个事件可以获取购买更新。 + */ + PURCHASES_UPDATED = 'purchases_updated', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 查询产品详细信息操作完成时触发。 + */ + PRODUCT_DETAILS_RESPONSE = 'product_details_response', + /** + * @en + * Called to notify that the query purchases operation has finished. + * + * @zh + * 查询购买操作完成时触发。 + */ + QUERY_PURCHASES_RESPONSE = 'query_purchases_response', + /** + * @en + * Called to notify that a consume operation has finished. + * + * @zh + * 消费操作完成时触发。 + */ + CONSUME_RESPONSE = 'consume_response', + /** + * @en + * Called to notify that an acknowledge purchase operation has finished. + * + * @zh + * 确认购买操作完成时触发。 + */ + ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', + /** + * @en + * Called to notify when the get billing config flow has finished. + * + * @zh + * 获取Billing配置流程完成时触发。 + */ + BILLING_CONFIG_RESPONSE = 'billing_config_response', + /** + * @en + * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', + /** + * @en + * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', + /** + * @en + * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', + /** + * @en + * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', + /** + * @en + * Called to notify that the alternative billing only dialog flow is finished. + * + * @zh + * 当仅替代Billing对话流程已完成时触发。 + */ + ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', + /** + * @en + * Called to notify that the external offer information dialog flow is finished. + * + * @zh + * 当外部报价信息对话流程已完成时触发。 + */ + EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', + /** + * @en + * Called to notify when the in-app messaging flow has finished. + * + * @zh + * 当应用内消息流程完成时触发。 + */ + IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', + + } + + /** + * @en + * Supported Product types. + * + * @zh + * 支持的产品类型。 + */ + export enum ProductType { + /** + * @en + * A Product type for Android apps in-app products. + * + * @zh + * Android 应用内产品的产品类型。 + */ + INAPP = 'inapp', + /** + * @en + * A Product type for Android apps subscriptions. + * + * @zh + * Android 应用程序订阅的产品类型。 + */ + SUBS = 'subs' + } + + /** + * @en + * Possible response codes. + * + * @zh + * 可能的响应代码。 + */ + export enum BillingResponseCode { + /** + * @en + * This field is deprecated. + * See SERVICE_UNAVAILABLE which will be used instead of this code. + * + * @zh + * 这个字段已经废弃。 + * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 + */ + SERVICE_TIMEOUT = -3, + /** + * @en + * The requested feature is not supported by the Play Store on the current device. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + FEATURE_NOT_SUPPORTED = -2, + /** + * @en + * The app is not connected to the Play Store service via the Google Play Billing Library. + * + * @zh + * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 + */ + SERVICE_DISCONNECTED = -1, + /** + * @en + * Success. + * + * @zh + * 成功。 + */ + OK = 0, + /** + * @en + * Transaction was canceled by the user. + * + * @zh + * 交易已被用户取消。 + */ + USER_CANCELED = 1, + /** + * @en + * The service is currently unavailable. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + SERVICE_UNAVAILABLE = 2, + /** + * @en + * A user billing error occurred during processing. + * + * @zh + * 处理过程中出现用户billing错误。 + */ + BILLING_UNAVAILABLE = 3, + /** + * @en + * The requested product is not available for purchase. + * + * @zh + * 所请求的产品无法购买。 + */ + ITEM_UNAVAILABLE = 4, + /** + * @en + * Error resulting from incorrect usage of the API. + * + * @zh + * 由于错误使用 API 而导致的错误。 + */ + DEVELOPER_ERROR = 5, + /** + * @en + * Fatal error during the API action. + * + * @zh + * API 操作期间发生致命错误。 + */ + ERROR = 6, + /** + * @en + * The purchase failed because the item is already owned. + * + * @zh + * 购买失败,因为该物品已被拥有。 + */ + ITEM_ALREADY_OWNED = 7, + /** + * @en + * Requested action on the item failed since it is not owned by the user. + * + * @zh + * 由于该项目不属于用户,因此对该项目请求的操作失败。 + */ + ITEM_NOT_OWNED = 8, + /** + * @en + * A network error occurred during the operation. + * + * @zh + * 操作期间发生网络错误。 + */ + NETWORK_ERROR = 12, + } + + /** + * @en + * Recurrence mode of the pricing phase. + * + * @zh + * 定价阶段的复现模式。 + */ + export enum RecurrenceMode { + /** + * @en + * The billing plan payment recurs for infinite billing periods unless cancelled. + * + * @zh + * 除非取消,否则billing计划付款将无限期地重复。 + */ + INFINITE_RECURRING = 1, + /** + * @en + * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. + * + * @zh + * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 + */ + FINITE_RECURRING = 2, + /** + * @en + * The billing plan payment is a one time charge that does not repeat. + * + * @zh + * Billing计划付款是一次性费用,不会重复。 + */ + NON_RECURRING = 3, + } + + /** + * @en + * Connection state of billing client. + * + * @zh + * Billing client的连接状态 + */ + export enum ConnectionState { + /** + * @en + * This client was not yet connected to billing service or was already closed. + * + * @zh + * 此客户端尚未连接到Billing服务或已关闭。 + */ + DISCONNECTED = 0, + /** + * @en + * This client is currently in process of connecting to billing service. + * + * @zh + * 此客户端目前正在连接到Billing服务。 + */ + CONNECTING = 1, + /** + * @en + * This client is currently connected to billing service. + * + * @zh + * 此客户端当前已连接到Billing服务。 + */ + CONNECTED = 2, + /** + * @en + * This client was already closed and shouldn't be used again. + * + * @zh + * 该客户端已关闭,不应再次使用。 + */ + CLOSED = 3, + } + + /** + * @en + * Features/capabilities supported by isFeatureSupported. + * + * @zh + * 支持的特性/能力isFeatureSupported。 + */ + export enum FeatureType { + /** + * @en + * Alternative billing only. + * + * @zh + * 仅限替代Billing。 + */ + ALTERNATIVE_BILLING_ONLY = 'jjj', + /** + * @en + * Get billing config. + * + * @zh + * 获取计费配置。 + */ + BILLING_CONFIG = 'ggg', + /** + * @en + * Play billing library support for external offer. + * + * @zh + * Play billing库支持外部报价。 + */ + EXTERNAL_OFFER = 'kkk', + /** + * @en + * Show in-app messages. + * + * @zh + * 显示应用内消息。 + */ + IN_APP_MESSAGING = 'bbb', + /** + * @en + * Launch a price change confirmation flow. + * + * @zh + * 启动价格变动确认流程。 + */ + PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', + /** + * @en + * Play billing library support for querying and purchasing. + * + * @zh + * Play Billing库支持查询、购买。 + */ + PRODUCT_DETAILS = 'fff', + /** + * @en + * Purchase/query for subscriptions. + * + * @zh + * 购买/查询订阅。 + */ + SUBSCRIPTIONS = 'subscriptions', + /** + * @en + * Subscriptions update/replace. + * + * @zh + * 订阅更新/替换。 + */ + UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', + } + + /** + * @en + * Possible purchase states. + * + * @zh + * 可能的购买状态。 + */ + export enum PurchaseState { + /** + * @en + * Purchase is pending and not yet completed to be processed by your app. + * + * @zh + * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 + */ + PENDING = 2, + /** + * @en + * Purchase is completed.. + * + * @zh + * 购买完成。 + */ + PURCHASED = 1, + /** + * @en + * Purchase with unknown state. + * + * @zh + * 未知状态 + */ + UNSPECIFIED_STATE = 0, + } + + /** + * @en + * Possible response codes. + * + * @zh + * InAppMessage可能的影响代码。 + */ + export enum InAppMessageResponseCode { + /** + * @en + * The flow has finished and there is no action needed from developers. + * + * @zh + * 流程已完成,开发人员无需采取任何行动。 + */ + NO_ACTION_NEEDED = 0, + /** + * @en + * The subscription status changed. + * + * @zh + * 订阅状态已改变。 + */ + SUBSCRIPTION_STATUS_UPDATED = 1, + } + + export type BillingResult = jsb.BillingResult; + export type OneTimePurchaseOfferDetails = jsb.OneTimePurchaseOfferDetails; + export type InstallmentPlanDetails = jsb.InstallmentPlanDetails; + export type PricingPhase = jsb.PricingPhase; + export type SubscriptionOfferDetails = jsb.SubscriptionOfferDetails; + export type ProductDetails = jsb.ProductDetails; + export type AccountIdentifiers = jsb.AccountIdentifiers; + export type PendingPurchaseUpdate = jsb.PendingPurchaseUpdate; + export type Purchase = jsb.Purchase; + export type BillingConfig = jsb.BillingConfig; + export type AlternativeBillingOnlyReportingDetails = jsb.AlternativeBillingOnlyReportingDetails; + export type ExternalOfferReportingDetails = jsb.ExternalOfferReportingDetails; + export type InAppMessageResult = jsb.InAppMessageResult; + + /** + * @en + * Interface for Google Play blling module. + * + * @zh + * Google Play blling模块的接口。 + * + */ + export const billing = new GooglePlayBilling(); +} + +interface BillingEventMap { + [googleplay.BillingEventType.BILLING_SETUP_FINISHED]: (result: googleplay.BillingResult) => void, + [googleplay.BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, + [googleplay.BillingEventType.PRODUCT_DETAILS_RESPONSE]: + (result: googleplay.BillingResult, productDetailsList: googleplay.ProductDetails[]) => void, + [googleplay.BillingEventType.PURCHASES_UPDATED]: (result: googleplay.BillingResult, purchases: googleplay.Purchase[]) => void, + [googleplay.BillingEventType.CONSUME_RESPONSE]: (result: googleplay.BillingResult, purchaseToken: string) => void, + [googleplay.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE]: (result: googleplay.BillingResult) => void + [googleplay.BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: googleplay.BillingResult, purchases: googleplay.Purchase[]) => void, + [googleplay.BillingEventType.BILLING_CONFIG_RESPONSE]: (result: googleplay.BillingResult, config: googleplay.BillingConfig) => void + [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: + (result: googleplay.BillingResult, alternativeBillingOnlyReportingDetails: googleplay.AlternativeBillingOnlyReportingDetails) => void + [googleplay.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: + (result: googleplay.BillingResult, externalOfferReportingDetails: googleplay.ExternalOfferReportingDetails) => void + [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: googleplay.BillingResult) => void + [googleplay.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: googleplay.BillingResult) => void + [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: googleplay.BillingResult) => void + [googleplay.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: googleplay.BillingResult) => void + [googleplay.BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: googleplay.InAppMessageResult) => void +} + +class GooglePlayBilling { + private _eventTarget: EventTarget = new EventTarget(); + constructor () { + if (!JSB || !jsb.billing) { + return; + } + jsb.onBillingSetupFinished = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.BILLING_SETUP_FINISHED, result); + }; + + jsb.onBillingServiceDisconnected = (): void => { + this._eventTarget.emit(googleplay.BillingEventType.BILLING_SERVICE_DISCONNECTED); + }; + + jsb.onProductDetailsResponse = ( + result: googleplay.BillingResult, + productDetailsList: googleplay.ProductDetails[], + ): void => { + this._eventTarget.emit(googleplay.BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); + }; + + jsb.onPurchasesUpdated = ( + result: googleplay.BillingResult, + purchaseList: googleplay.Purchase[], + ): void => { + this._eventTarget.emit(googleplay.BillingEventType.PURCHASES_UPDATED, result, purchaseList); + }; + + jsb.onConsumeResponse = ( + result: googleplay.BillingResult, + purchaseToken: string, + ): void => { + this._eventTarget.emit(googleplay.BillingEventType.CONSUME_RESPONSE, result, purchaseToken); + }; + + jsb.onAcknowledgePurchaseResponse = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); + }; + + jsb.onQueryPurchasesResponse = ( + result: googleplay.BillingResult, + purchaseList: googleplay.Purchase[], + ): void => { + this._eventTarget.emit(googleplay.BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); + }; + + jsb.onBillingConfigResponse = (result: googleplay.BillingResult, config: googleplay.BillingConfig): void => { + this._eventTarget.emit(googleplay.BillingEventType.BILLING_CONFIG_RESPONSE, result, config); + }; + + jsb.onAlternativeBillingOnlyTokenResponse = (result: googleplay.BillingResult, details: googleplay.AlternativeBillingOnlyReportingDetails): void => { + this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); + }; + + jsb.onExternalOfferReportingDetailsResponse = (result: googleplay.BillingResult, details: googleplay.ExternalOfferReportingDetails): void => { + this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); + }; + + jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); + }; + + jsb.onExternalOfferAvailabilityResponse = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); + }; + + jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onExternalOfferInformationDialogResponse = (result: googleplay.BillingResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onInAppMessageResponse = (result: googleplay.InAppMessageResult): void => { + this._eventTarget.emit(googleplay.BillingEventType.IN_APP_MESSAGE_RESPONSE, result); + }; + } + + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + public startConnection (): void { + jsb.billing?.startConnection(); + } + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + public endConnection (): void { + jsb.billing?.endConnection(); + } + + /** + * @en Get the current billing client connection state. + * @zh 获取当前billing客户端连接状态。 + */ + public getConnectionState (): number { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.getConnectionState(); + } + return googleplay.ConnectionState.DISCONNECTED; + } + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + public isReady (): boolean { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isReady(); + } + return false; + } + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + * @param productId @zh 产品ID。 @en product id. + * @param productType @zh 产品类型。 @en product type. + * + */ + public queryProductDetailsParams (productId: string[] | string, productType: googleplay.ProductType): void { + if (productId instanceof Array) { + jsb.billing?.queryProductDetailsParams(productId, productType); + } else { + const productIdList: string[] = []; + productIdList.push(productId); + jsb.billing?.queryProductDetailsParams(productIdList, productType); + } + } + + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + * @param productDetails @zh 产品详情。 @en product details. + * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. + */ + public launchBillingFlow (productDetails: googleplay.ProductDetails[] | googleplay.ProductDetails, selectedOfferToken: string | null): void { + if (productDetails instanceof Array) { + jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); + } else { + const productDetailsList: googleplay.ProductDetails[] = []; + productDetailsList.push(productDetails); + jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); + } + } + + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public consumePurchases (purchase: googleplay.Purchase[] | googleplay.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.consumePurchases(purchase); + } else { + const purchaseList: googleplay.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.consumePurchases(purchaseList); + } + } + + /** + * @en Acknowledges in-app purchases. + * @zh 确认应用内购买。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public acknowledgePurchase (purchase: googleplay.Purchase[] | googleplay.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.acknowledgePurchase(purchase); + } else { + const purchaseList: googleplay.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.acknowledgePurchase(purchaseList); + } + } + + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + * @param productType @zh 产品类型 @en Product type. + */ + public queryPurchasesAsync (productType: googleplay.ProductType): void { + jsb.billing?.queryPurchasesAsync(productType); + } + + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + public getBillingConfigAsync (): void { + jsb.billing?.getBillingConfigAsync(); + } + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made + * via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + */ + public createAlternativeBillingOnlyReportingDetailsAsync (): void { + jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); + } + + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + */ + public isAlternativeBillingOnlyAvailableAsync (): void { + jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); + } + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + public createExternalOfferReportingDetailsAsync (): void { + jsb.billing?.createExternalOfferReportingDetailsAsync(); + } + + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + public isExternalOfferAvailableAsync (): void { + jsb.billing?.isExternalOfferAvailableAsync(); + } + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + * @param feature @zh 功能特性 @en feature. + */ + public isFeatureSupported (feature: string): googleplay.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isFeatureSupported(feature); + } + return null; + } + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + public showAlternativeBillingOnlyInformationDialog (): googleplay.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showAlternativeBillingOnlyInformationDialog(); + } + return null; + } + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + public showExternalOfferInformationDialog (): googleplay.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showExternalOfferInformationDialog(); + } + return null; + } + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + public showInAppMessages (): googleplay.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showInAppMessages(); + } + return null; + } + + public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.on(type, callback, target); + return callback; + } + public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.once(type, callback, target); + return callback; + } + public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { + this._eventTarget.off(eventType, callback, target); + } +} diff --git a/vendor/google/billing/billing.ts b/vendor/google/billing/billing.ts new file mode 100644 index 00000000000..d2fc57f3397 --- /dev/null +++ b/vendor/google/billing/billing.ts @@ -0,0 +1,853 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ +import { JSB } from 'internal:constants'; +import { EventTarget } from '../../../../cocos/core/event'; + +interface BillingEventMap { + [google.BillingEventType.BILLING_SETUP_FINISHED]: (result: google.BillingResult) => void, + [google.BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, + [google.BillingEventType.PRODUCT_DETAILS_RESPONSE]: + (result: google.BillingResult, productDetailsList: google.ProductDetails[]) => void, + [google.BillingEventType.PURCHASES_UPDATED]: (result: google.BillingResult, purchases: google.Purchase[]) => void, + [google.BillingEventType.CONSUME_RESPONSE]: (result: google.BillingResult, purchaseToken: string) => void, + [google.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: google.BillingResult, purchases: google.Purchase[]) => void, + [google.BillingEventType.BILLING_CONFIG_RESPONSE]: (result: google.BillingResult, config: google.BillingConfig) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: + (result: google.BillingResult, alternativeBillingOnlyReportingDetails: google.AlternativeBillingOnlyReportingDetails) => void + [google.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: + (result: google.BillingResult, externalOfferReportingDetails: google.ExternalOfferReportingDetails) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: google.InAppMessageResult) => void +} + +class Billing { + private _eventTarget: EventTarget = new EventTarget(); + constructor () { + if (!JSB || !jsb.billing) { + return; + } + jsb.onBillingSetupFinished = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_SETUP_FINISHED, result); + }; + + jsb.onBillingServiceDisconnected = (): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_SERVICE_DISCONNECTED); + }; + + jsb.onProductDetailsResponse = ( + result: google.BillingResult, + productDetailsList: google.ProductDetails[], + ): void => { + this._eventTarget.emit(google.BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); + }; + + jsb.onPurchasesUpdated = ( + result: google.BillingResult, + purchaseList: google.Purchase[], + ): void => { + this._eventTarget.emit(google.BillingEventType.PURCHASES_UPDATED, result, purchaseList); + }; + + jsb.onConsumeResponse = ( + result: google.BillingResult, + purchaseToken: string, + ): void => { + this._eventTarget.emit(google.BillingEventType.CONSUME_RESPONSE, result, purchaseToken); + }; + + jsb.onAcknowledgePurchaseResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); + }; + + jsb.onQueryPurchasesResponse = ( + result: google.BillingResult, + purchaseList: google.Purchase[], + ): void => { + this._eventTarget.emit(google.BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); + }; + + jsb.onBillingConfigResponse = (result: google.BillingResult, config: google.BillingConfig): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_CONFIG_RESPONSE, result, config); + }; + + jsb.onAlternativeBillingOnlyTokenResponse = ( + result: google.BillingResult, + details: google.AlternativeBillingOnlyReportingDetails, + ): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); + }; + + jsb.onExternalOfferReportingDetailsResponse = (result: google.BillingResult, details: google.ExternalOfferReportingDetails): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); + }; + + jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); + }; + + jsb.onExternalOfferAvailabilityResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); + }; + + jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onExternalOfferInformationDialogResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onInAppMessageResponse = (result: google.InAppMessageResult): void => { + this._eventTarget.emit(google.BillingEventType.IN_APP_MESSAGE_RESPONSE, result); + }; + } + + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + public startConnection (): void { + jsb.billing?.startConnection(); + } + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + public endConnection (): void { + jsb.billing?.endConnection(); + } + + /** + * @en Get the current billing client connection state. + * @zh 获取当前billing客户端连接状态。 + */ + public getConnectionState (): number { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.getConnectionState(); + } + return google.ConnectionState.DISCONNECTED; + } + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + public isReady (): boolean { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isReady(); + } + return false; + } + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + * @param productId @zh 产品ID。 @en product id. + * @param productType @zh 产品类型。 @en product type. + * + */ + public queryProductDetailsParams (productId: string[] | string, productType: google.ProductType): void { + if (productId instanceof Array) { + jsb.billing?.queryProductDetailsParams(productId, productType); + } else { + const productIdList: string[] = []; + productIdList.push(productId); + jsb.billing?.queryProductDetailsParams(productIdList, productType); + } + } + + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + * @param productDetails @zh 产品详情。 @en product details. + * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. + */ + public launchBillingFlow (productDetails: google.ProductDetails[] | google.ProductDetails, selectedOfferToken: string | null): void { + if (productDetails instanceof Array) { + jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); + } else { + const productDetailsList: google.ProductDetails[] = []; + productDetailsList.push(productDetails); + jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); + } + } + + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public consumePurchases (purchase: google.Purchase[] | google.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.consumePurchases(purchase); + } else { + const purchaseList: google.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.consumePurchases(purchaseList); + } + } + + /** + * @en Acknowledges in-app purchases. + * @zh 确认应用内购买。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public acknowledgePurchase (purchase: google.Purchase[] | google.Purchase): void { + if (purchase instanceof Array) { + jsb.billing?.acknowledgePurchase(purchase); + } else { + const purchaseList: google.Purchase[] = []; + purchaseList.push(purchase); + jsb.billing?.acknowledgePurchase(purchaseList); + } + } + + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + * @param productType @zh 产品类型 @en Product type. + */ + public queryPurchasesAsync (productType: google.ProductType): void { + jsb.billing?.queryPurchasesAsync(productType); + } + + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + public getBillingConfigAsync (): void { + jsb.billing?.getBillingConfigAsync(); + } + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made + * via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + */ + public createAlternativeBillingOnlyReportingDetailsAsync (): void { + jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); + } + + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + */ + public isAlternativeBillingOnlyAvailableAsync (): void { + jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); + } + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + public createExternalOfferReportingDetailsAsync (): void { + jsb.billing?.createExternalOfferReportingDetailsAsync(); + } + + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + public isExternalOfferAvailableAsync (): void { + jsb.billing?.isExternalOfferAvailableAsync(); + } + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + * @param feature @zh 功能特性 @en feature. + */ + public isFeatureSupported (feature: string): google.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.isFeatureSupported(feature); + } + return null; + } + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + public showAlternativeBillingOnlyInformationDialog (): google.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showAlternativeBillingOnlyInformationDialog(); + } + return null; + } + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + public showExternalOfferInformationDialog (): google.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showExternalOfferInformationDialog(); + } + return null; + } + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + public showInAppMessages (): google.BillingResult | null { + if (jsb.billing) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.billing.showInAppMessages(); + } + return null; + } + + public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.on(type, callback, target); + return callback; + } + public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.once(type, callback, target); + return callback; + } + public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { + this._eventTarget.off(eventType, callback, target); + } +} + +const gpBilling = new Billing(); + +export namespace google { + /** + * @en Google Play Billing event type + * @zh Google Play Billing事件类型 + */ + export enum BillingEventType { + /** + * @en + * Called to notify that setup is complete. + * + * @zh + * 当安装已经完成时触发。 + */ + BILLING_SETUP_FINISHED = 'billing_setup_finished', + /** + * @en + * Called to notify that the connection to the billing service was lost. + * + * @zh + * 当Billing服务连接断开时触发。 + */ + BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', + /** + * @en + * Listen to this event to get notifications of purchase updates. + * + * @zh + * 监听这个事件可以获取购买更新。 + */ + PURCHASES_UPDATED = 'purchases_updated', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 查询产品详细信息操作完成时触发。 + */ + PRODUCT_DETAILS_RESPONSE = 'product_details_response', + /** + * @en + * Called to notify that the query purchases operation has finished. + * + * @zh + * 查询购买操作完成时触发。 + */ + QUERY_PURCHASES_RESPONSE = 'query_purchases_response', + /** + * @en + * Called to notify that a consume operation has finished. + * + * @zh + * 消费操作完成时触发。 + */ + CONSUME_RESPONSE = 'consume_response', + /** + * @en + * Called to notify that an acknowledge purchase operation has finished. + * + * @zh + * 确认购买操作完成时触发。 + */ + ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', + /** + * @en + * Called to notify when the get billing config flow has finished. + * + * @zh + * 获取Billing配置流程完成时触发。 + */ + BILLING_CONFIG_RESPONSE = 'billing_config_response', + /** + * @en + * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', + /** + * @en + * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', + /** + * @en + * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', + /** + * @en + * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', + /** + * @en + * Called to notify that the alternative billing only dialog flow is finished. + * + * @zh + * 当仅替代Billing对话流程已完成时触发。 + */ + ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', + /** + * @en + * Called to notify that the external offer information dialog flow is finished. + * + * @zh + * 当外部报价信息对话流程已完成时触发。 + */ + EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', + /** + * @en + * Called to notify when the in-app messaging flow has finished. + * + * @zh + * 当应用内消息流程完成时触发。 + */ + IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', + + } + + /** + * @en + * Supported Product types. + * + * @zh + * 支持的产品类型。 + */ + export enum ProductType { + /** + * @en + * A Product type for Android apps in-app products. + * + * @zh + * Android 应用内产品的产品类型。 + */ + INAPP = 'inapp', + /** + * @en + * A Product type for Android apps subscriptions. + * + * @zh + * Android 应用程序订阅的产品类型。 + */ + SUBS = 'subs' + } + + /** + * @en + * Possible response codes. + * + * @zh + * 可能的响应代码。 + */ + export enum BillingResponseCode { + /** + * @en + * This field is deprecated. + * See SERVICE_UNAVAILABLE which will be used instead of this code. + * + * @zh + * 这个字段已经废弃。 + * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 + */ + SERVICE_TIMEOUT = -3, + /** + * @en + * The requested feature is not supported by the Play Store on the current device. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + FEATURE_NOT_SUPPORTED = -2, + /** + * @en + * The app is not connected to the Play Store service via the Google Play Billing Library. + * + * @zh + * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 + */ + SERVICE_DISCONNECTED = -1, + /** + * @en + * Success. + * + * @zh + * 成功。 + */ + OK = 0, + /** + * @en + * Transaction was canceled by the user. + * + * @zh + * 交易已被用户取消。 + */ + USER_CANCELED = 1, + /** + * @en + * The service is currently unavailable. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + SERVICE_UNAVAILABLE = 2, + /** + * @en + * A user billing error occurred during processing. + * + * @zh + * 处理过程中出现用户billing错误。 + */ + BILLING_UNAVAILABLE = 3, + /** + * @en + * The requested product is not available for purchase. + * + * @zh + * 所请求的产品无法购买。 + */ + ITEM_UNAVAILABLE = 4, + /** + * @en + * Error resulting from incorrect usage of the API. + * + * @zh + * 由于错误使用 API 而导致的错误。 + */ + DEVELOPER_ERROR = 5, + /** + * @en + * Fatal error during the API action. + * + * @zh + * API 操作期间发生致命错误。 + */ + ERROR = 6, + /** + * @en + * The purchase failed because the item is already owned. + * + * @zh + * 购买失败,因为该物品已被拥有。 + */ + ITEM_ALREADY_OWNED = 7, + /** + * @en + * Requested action on the item failed since it is not owned by the user. + * + * @zh + * 由于该项目不属于用户,因此对该项目请求的操作失败。 + */ + ITEM_NOT_OWNED = 8, + /** + * @en + * A network error occurred during the operation. + * + * @zh + * 操作期间发生网络错误。 + */ + NETWORK_ERROR = 12, + } + + /** + * @en + * Recurrence mode of the pricing phase. + * + * @zh + * 定价阶段的复现模式。 + */ + export enum RecurrenceMode { + /** + * @en + * The billing plan payment recurs for infinite billing periods unless cancelled. + * + * @zh + * 除非取消,否则billing计划付款将无限期地重复。 + */ + INFINITE_RECURRING = 1, + /** + * @en + * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. + * + * @zh + * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 + */ + FINITE_RECURRING = 2, + /** + * @en + * The billing plan payment is a one time charge that does not repeat. + * + * @zh + * Billing计划付款是一次性费用,不会重复。 + */ + NON_RECURRING = 3, + } + + /** + * @en + * Connection state of billing client. + * + * @zh + * Billing client的连接状态 + */ + export enum ConnectionState { + /** + * @en + * This client was not yet connected to billing service or was already closed. + * + * @zh + * 此客户端尚未连接到Billing服务或已关闭。 + */ + DISCONNECTED = 0, + /** + * @en + * This client is currently in process of connecting to billing service. + * + * @zh + * 此客户端目前正在连接到Billing服务。 + */ + CONNECTING = 1, + /** + * @en + * This client is currently connected to billing service. + * + * @zh + * 此客户端当前已连接到Billing服务。 + */ + CONNECTED = 2, + /** + * @en + * This client was already closed and shouldn't be used again. + * + * @zh + * 该客户端已关闭,不应再次使用。 + */ + CLOSED = 3, + } + + /** + * @en + * Features/capabilities supported by isFeatureSupported. + * + * @zh + * 支持的特性/能力isFeatureSupported。 + */ + export enum FeatureType { + /** + * @en + * Alternative billing only. + * + * @zh + * 仅限替代Billing。 + */ + ALTERNATIVE_BILLING_ONLY = 'jjj', + /** + * @en + * Get billing config. + * + * @zh + * 获取计费配置。 + */ + BILLING_CONFIG = 'ggg', + /** + * @en + * Play billing library support for external offer. + * + * @zh + * Play billing库支持外部报价。 + */ + EXTERNAL_OFFER = 'kkk', + /** + * @en + * Show in-app messages. + * + * @zh + * 显示应用内消息。 + */ + IN_APP_MESSAGING = 'bbb', + /** + * @en + * Launch a price change confirmation flow. + * + * @zh + * 启动价格变动确认流程。 + */ + PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', + /** + * @en + * Play billing library support for querying and purchasing. + * + * @zh + * Play Billing库支持查询、购买。 + */ + PRODUCT_DETAILS = 'fff', + /** + * @en + * Purchase/query for subscriptions. + * + * @zh + * 购买/查询订阅。 + */ + SUBSCRIPTIONS = 'subscriptions', + /** + * @en + * Subscriptions update/replace. + * + * @zh + * 订阅更新/替换。 + */ + UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', + } + + /** + * @en + * Possible purchase states. + * + * @zh + * 可能的购买状态。 + */ + export enum PurchaseState { + /** + * @en + * Purchase is pending and not yet completed to be processed by your app. + * + * @zh + * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 + */ + PENDING = 2, + /** + * @en + * Purchase is completed.. + * + * @zh + * 购买完成。 + */ + PURCHASED = 1, + /** + * @en + * Purchase with unknown state. + * + * @zh + * 未知状态 + */ + UNSPECIFIED_STATE = 0, + } + + /** + * @en + * Possible response codes. + * + * @zh + * InAppMessage可能的影响代码。 + */ + export enum InAppMessageResponseCode { + /** + * @en + * The flow has finished and there is no action needed from developers. + * + * @zh + * 流程已完成,开发人员无需采取任何行动。 + */ + NO_ACTION_NEEDED = 0, + /** + * @en + * The subscription status changed. + * + * @zh + * 订阅状态已改变。 + */ + SUBSCRIPTION_STATUS_UPDATED = 1, + } + + export type BillingResult = jsb.BillingResult; + export type OneTimePurchaseOfferDetails = jsb.OneTimePurchaseOfferDetails; + export type InstallmentPlanDetails = jsb.InstallmentPlanDetails; + export type PricingPhase = jsb.PricingPhase; + export type SubscriptionOfferDetails = jsb.SubscriptionOfferDetails; + export type ProductDetails = jsb.ProductDetails; + export type AccountIdentifiers = jsb.AccountIdentifiers; + export type PendingPurchaseUpdate = jsb.PendingPurchaseUpdate; + export type Purchase = jsb.Purchase; + export type BillingConfig = jsb.BillingConfig; + export type AlternativeBillingOnlyReportingDetails = jsb.AlternativeBillingOnlyReportingDetails; + export type ExternalOfferReportingDetails = jsb.ExternalOfferReportingDetails; + export type InAppMessageResult = jsb.InAppMessageResult; + + /** + * @en + * Interface for Google Play blling module. + * + * @zh + * Google Play blling模块的接口。 + * + */ + export const billing = gpBilling; +} From c2dff05801211ce0dc403f7c11f21636fda4add9 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Tue, 26 Nov 2024 11:54:21 +0800 Subject: [PATCH 06/13] Rename file name --- vendor/google/{ => play}/billing/billing.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vendor/google/{ => play}/billing/billing.ts (100%) diff --git a/vendor/google/billing/billing.ts b/vendor/google/play/billing/billing.ts similarity index 100% rename from vendor/google/billing/billing.ts rename to vendor/google/play/billing/billing.ts From 6fb3e26d106aa5c29b4c50c04b0f5a1fd0a9c5f8 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Tue, 26 Nov 2024 11:54:21 +0800 Subject: [PATCH 07/13] Rename file name --- @types/jsb.d.ts | 12 +++--- .../com/cocos/billing/CocosBilling.java | 43 +++++++++++++++---- .../com/cocos/billing/CocosBillingHelper.java | 20 ++++++--- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/@types/jsb.d.ts b/@types/jsb.d.ts index e2a593c05de..11262fdd1e6 100644 --- a/@types/jsb.d.ts +++ b/@types/jsb.d.ts @@ -672,8 +672,8 @@ declare namespace jsb { } /** - * @en The details used to report transactions made via alternative billing without user choice to use Google Play billing. - * @zh 用于报告用户未选择使用 Google Play 结算方式而通过替代结算方式进行的交易的详细信息。 + * @en The details used to report transactions made via alternative billing without user choice to use Google Play Billing. + * @zh 用于报告用户未选择使用 Google Play Billing方式而通过替代Billing方式进行的交易的详细信息。 */ export interface AlternativeBillingOnlyReportingDetails { /** @@ -714,7 +714,7 @@ declare namespace jsb { } /** - * @en Main interface for communication between the google play library and user application code. + * @en Main interface for communication between the Google Play library and user application code. * @zh 产品 ID。 */ const billing: { @@ -777,12 +777,12 @@ declare namespace jsb { /** * @en Creates alternative billing only purchase details that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. - * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play Billing。 */ createAlternativeBillingOnlyReportingDetailsAsync(): void; /** - * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. - * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + * @en Checks the availability of offering alternative billing without user choice to use Google Play Billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play Billing方式。 */ isAlternativeBillingOnlyAvailableAsync(): void; diff --git a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java index 3033144cc9f..a4b821c8f8c 100644 --- a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java +++ b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java @@ -22,7 +22,7 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -package com.cocos.lib; +package com.cocos.billing; import android.util.Log; import androidx.annotation.NonNull; @@ -57,8 +57,8 @@ of this software and associated documentation files (the "Software"), to deal import com.android.billingclient.api.ExternalOfferInformationDialogListener; import com.android.billingclient.api.InAppMessageParams; import com.android.billingclient.api.InAppMessageResponseListener; - - +import com.cocos.lib.GlobalObject; +import com.cocos.lib.CocosHelper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -73,6 +73,8 @@ public class CocosBilling implements BillingClientStateListener, private static final String TAG = CocosBilling.class.getSimpleName(); private Map _productDetails = new HashMap<>(); private Map _purchase = new HashMap<>(); + private int _productDetailsNextID = 0; + private int _purchaseNextID = 0; /** * The billing client. @@ -86,6 +88,22 @@ public CocosBilling() { .build(); } + public void removeProductDetails(int productDetailsID) { + if (_productDetails.containsKey(productDetailsID)) { + _productDetails.remove(productDetailsID); + } else { + Log.w(TAG, "Remove invalid product details id"); + } + } + + public void removePurchase(int purchaseID) { + if (_purchase.containsKey(purchaseID)) { + _purchase.remove(purchaseID); + } else { + Log.w(TAG, "Remove invalid purchase id"); + } + } + public void startConnection() { _billingClient.startConnection(this); } @@ -181,6 +199,10 @@ public void launchBillingFlow(int[] productDetailsHashs, String selectedOfferTok } } + if(productDetailsParamsList.isEmpty()) { + Log.w(TAG, "Purchased product ID does not exist"); + return; + } BillingFlowParams params = BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParamsList).build(); _billingClient.launchBillingFlow(GlobalObject.getActivity(), params); } @@ -322,13 +344,14 @@ public void run() { @Override public void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList) { + int startID = _productDetailsNextID; for (ProductDetails productDetails: productDetailsList) { - _productDetails.put(productDetails.hashCode(), productDetails); + _productDetails.put(_productDetailsNextID++, productDetails); } CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onProductDetailsResponse(billingResult, productDetailsList); + CocosBillingHelper.onProductDetailsResponse(billingResult, productDetailsList, startID); } }); } @@ -336,15 +359,16 @@ public void run() { @Override public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchaseList) { + int startID = _purchaseNextID; if(purchaseList != null) { for (Purchase purchase: purchaseList) { - _purchase.put(purchase.hashCode(), purchase); + _purchase.put(_purchaseNextID++, purchase); } } CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onPurchasesUpdated(billingResult, purchaseList); + CocosBillingHelper.onPurchasesUpdated(billingResult, purchaseList, startID); } }); } @@ -352,13 +376,14 @@ public void run() { @Override public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List purchaseList) { + int startID = _purchaseNextID; for (Purchase purchase: purchaseList) { - _purchase.put(purchase.hashCode(), purchase); + _purchase.put(_purchaseNextID++, purchase); } CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList); + CocosBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList, startID); } }); } diff --git a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java index b859b3e7860..5d862538f4d 100644 --- a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java +++ b/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java @@ -22,7 +22,7 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -package com.cocos.lib; +package com.cocos.billing; import android.annotation.SuppressLint; @@ -37,15 +37,15 @@ of this software and associated documentation files (the "Software"), to deal import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; import java.util.List; - -import com.cocos.lib.CocosBilling; +import com.cocos.lib.GlobalObject; +import com.cocos.billing.CocosBilling; public class CocosBillingHelper { public static native void onBillingSetupFinished(@NonNull BillingResult billingResult); public static native void onBillingServiceDisconnected(); - public static native void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList); - public static native void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchasesList); - public static native void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List purchasesList); + public static native void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList, int startID); + public static native void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchasesList, int startID); + public static native void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List purchasesList, int startID); public static native void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String purchaseToken); public static native void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult); public static native void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nullable BillingConfig billingConfig); @@ -73,6 +73,14 @@ public static CocosBilling getInstance() { return instance; } + public static void removeProductDetails(int productDetailsID) { + instance.removeProductDetails(productDetailsID); + } + + public static void removePurchase(int purchaseID) { + instance.removeProductDetails(purchaseID); + } + public static void startConnection() { GlobalObject.runOnUiThread(new Runnable() { @Override From 8c7589abb101483b86a882dbfb54bdf963208b08 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Tue, 26 Nov 2024 14:14:44 +0800 Subject: [PATCH 08/13] Remove redundant files --- exports/vendor.ts | 26 + .../source/platforms/google-play.ts | 590 ++++++++++++ vender/google/play/billing/billing.ts | 851 ------------------ vendor/google/play/index.ts | 25 + 4 files changed, 641 insertions(+), 851 deletions(-) create mode 100644 exports/vendor.ts create mode 100644 scripts/native-pack-tool/source/platforms/google-play.ts delete mode 100644 vender/google/play/billing/billing.ts create mode 100644 vendor/google/play/index.ts diff --git a/exports/vendor.ts b/exports/vendor.ts new file mode 100644 index 00000000000..e9aea6db849 --- /dev/null +++ b/exports/vendor.ts @@ -0,0 +1,26 @@ +/* + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +export * from '../vendor/google/play'; diff --git a/scripts/native-pack-tool/source/platforms/google-play.ts b/scripts/native-pack-tool/source/platforms/google-play.ts new file mode 100644 index 00000000000..1ce044567aa --- /dev/null +++ b/scripts/native-pack-tool/source/platforms/google-play.ts @@ -0,0 +1,590 @@ +import * as fs from 'fs-extra'; +import * as ps from 'path'; +import { CocosParams, NativePackTool } from "../base/default"; +import { cchelper, Paths } from "../utils"; +import * as URL from 'url'; +import { spawn, spawnSync } from 'child_process'; +import * as xml2js from 'xml2js'; +import { platform } from 'os'; + +export interface IOrientation { + landscapeLeft: boolean; + landscapeRight: boolean; + portrait: boolean; + upsideDown: boolean; +} + +export interface IAndroidParams { + packageName: string; + sdkPath: string; + ndkPath: string; + javaHome: string; + javaPath: string; + androidInstant: boolean, + maxAspectRatio: string; + remoteUrl?: string; + apiLevel: number; + appABIs: string[]; + keystorePassword: string; + keystoreAlias: string; + keystoreAliasPassword: string; + keystorePath: string; + inputSDK: boolean; + + orientation: IOrientation; + appBundle: boolean; + resizeableActivity: boolean; +} + +const DefaultAPILevel = 27; +export class GooglePlayPackTool extends NativePackTool { + params!: CocosParams; + + protected firstTimeBuild: boolean = false; + + protected async copyPlatformTemplate() { + // 原生工程不重复拷贝 TODO 复用前需要做版本检测 + if (!fs.existsSync(this.paths.nativePrjDir)) { + // 拷贝 lite 仓库的 templates/android/build 文件到构建输出目录 + await fs.copy(ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'build'), this.paths.nativePrjDir, { overwrite: false }); + this.firstTimeBuild = true; + } else { + this.firstTimeBuild = false; + } + // 原生工程不重复拷贝 TODO 复用前需要做版本检测 + if (!fs.existsSync(this.paths.platformTemplateDirInPrj)) { + // 拷贝 lite 仓库的 templates/android/template 文件到构建输出目录 + await fs.copy(ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'template'), this.paths.platformTemplateDirInPrj, { overwrite: false }); + this.writeEngineVersion(); + } else { + this.validateNativeDir(); + } + } + + protected validatePlatformDirectory(missing: string[]): void { + const srcDir = ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'template'); + const dstDir = this.paths.platformTemplateDirInPrj; + this.validateDirectory(srcDir, dstDir, missing); + } + + async create() { + await this.copyCommonTemplate(); + await this.copyPlatformTemplate(); + await this.generateCMakeConfig(); + await this.excuteCocosTemplateTask(); + + await this.updateAndroidGradleValues(); + await this.updateManifest(); + await this.encrypteScripts(); + await this.generateAppNameValues(); + return true; + } + + async make() { + const options = this.params.platformParams; + + if (options.javaHome) { + if (process.env.JAVA_HOME !== options.javaHome) { + process.env.JAVA_HOME = options.javaHome; + console.log(`Update JAVA_HOME to ${options.javaHome}`); + } + if (!process.env.PATH!.startsWith(options.javaHome)) { + const sep = platform() === 'win32' ? ';' : ':'; + process.env.PATH = ps.join(options.javaHome, 'bin') + sep + process.env.PATH; + console.log(`Add JAVA_HOME/bin to PATH`); + } + } + + const projDir: string = this.paths.nativePrjDir; + if (!fs.existsSync(projDir)) { + throw new Error(`dir ${projDir} not exits`); + } + let gradle = 'gradlew'; + if (process.platform === 'win32') { + gradle += '.bat'; + } else { + gradle = './' + gradle; + } + + let buildMode = ''; + const outputMode = this.params.debug ? 'Debug' : 'Release'; + + // compile android + buildMode = `${this.projectNameASCII()}:assemble${outputMode}`; + + // pushd + const originDir = process.cwd(); + try { + process.chdir(projDir); + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } catch (e) { + throw e; + } finally { + // popd + process.chdir(originDir); + } + + + // compile android-instant + if (options.androidInstant) { + buildMode = `instantapp:assemble${outputMode}`; + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } + + // compile google app bundle + if (options.appBundle) { + if (options.androidInstant) { + buildMode = `bundle${outputMode}`; + } else { + buildMode = `${this.params.projectName}:bundle${outputMode}`; + } + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } + return await this.copyToDist(); + } + + /** + * Deprecated, only be compatible with historical packaging tools + */ + protected async setOrientation() { + const cfg = this.params.platformParams.orientation; + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'app/AndroidManifest.xml'); + const instantManifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + if (fs.existsSync(manifestPath) && fs.existsSync(instantManifestPath)) { + const pattern = /android:screenOrientation="[^"]*"/; + let replaceString = 'android:screenOrientation="unspecified"'; + + if (cfg.landscapeRight && cfg.landscapeLeft && (cfg.portrait || cfg.upsideDown)) { + replaceString = 'android:screenOrientation="fullSensor"'; + } else if ((cfg.landscapeRight || cfg.landscapeLeft) && (cfg.portrait || cfg.upsideDown)) { + replaceString = 'android:screenOrientation="unspecified"'; + } else if (cfg.landscapeRight && !cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="landscape"'; + } else if (!cfg.landscapeRight && cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="reverseLandscape"'; + } else if (cfg.landscapeRight && cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="sensorLandscape"'; + } else if (cfg.portrait && !cfg.upsideDown) { + replaceString = 'android:screenOrientation="portrait"'; + } else if (!cfg.portrait && cfg.upsideDown) { + const oriValue = 'reversePortrait'; + replaceString = `android:screenOrientation="${oriValue}"`; + } else if (cfg.portrait && cfg.upsideDown) { + const oriValue = 'sensorPortrait'; + replaceString = `android:screenOrientation="${oriValue}"`; + } + + let content = await fs.readFile(manifestPath, 'utf8'); + content = content.replace(pattern, replaceString); + let instantContent = await fs.readFile(instantManifestPath, 'utf8'); + instantContent = instantContent.replace(pattern, replaceString); + await fs.writeFile(manifestPath, content); + await fs.writeFile(instantManifestPath, instantContent); + } + } + + private mapOrientationValue() { + const orientation = this.params.platformParams.orientation; + let orientationValue = 'unspecified'; + + if (orientation.landscapeRight && orientation.landscapeLeft && (orientation.portrait || orientation.upsideDown)) { + orientationValue = 'fullSensor'; + } else if ((orientation.landscapeRight || orientation.landscapeLeft) && (orientation.portrait || orientation.upsideDown)) { + orientationValue = 'unspecified'; + } else if (orientation.landscapeRight && !orientation.landscapeLeft) { + orientationValue = 'landscape'; + } else if (!orientation.landscapeRight && orientation.landscapeLeft) { + orientationValue = 'reverseLandscape'; + } else if (orientation.landscapeRight && orientation.landscapeLeft) { + orientationValue = 'sensorLandscape'; + } else if (orientation.portrait && !orientation.upsideDown) { + orientationValue = 'portrait'; + } else if (!orientation.portrait && orientation.upsideDown) { + orientationValue = 'reversePortrait'; + } else if (orientation.portrait && orientation.upsideDown) { + orientationValue = 'sensorPortrait'; + } + return orientationValue; + } + + protected async updateManifest() { + + if (!this.firstTimeBuild) { + console.log(`AndroidManifest.xml has already been updated!`); + return; + } + + const resizeableActivity: boolean = this.params.platformParams.resizeableActivity; + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'app/AndroidManifest.xml'); + const instantManifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + + const fnParseXml = async (xmlFile: string) => { + const xmlData = await fs.readFile(xmlFile, 'utf8'); + const data = await xml2js.parseStringPromise(xmlData); + return { + data, + save: async () => { + const builder = new xml2js.Builder(); + const dstXML = builder.buildObject(data); + await fs.writeFile(xmlFile, dstXML, 'utf8'); + } + } + }; + const fnUpdateOrientation = (data: any) => { + const attrRef = data.manifest.application[0].activity[0].$; + attrRef['android:screenOrientation'] = this.mapOrientationValue(); + }; + const fnUpdateResizeableActivity = (data: any) => { + const activityRef = data.manifest.application[0].$; + activityRef['android:resizeableActivity'] = resizeableActivity ? 'true' : 'false'; + }; + const fnUpdateMaxAspectRation = (data: any) => { + if (resizeableActivity) return; // disabled + const maxAspectRatio: string = this.params.platformParams.maxAspectRatio; + if (!maxAspectRatio) return; // value not set + const matchFrac = maxAspectRatio.match(/^(\d+):(\d+)$/); + let aspectRatioFloatValue; + try { + if (matchFrac) { + aspectRatioFloatValue = Number.parseInt(matchFrac[1], 10) / Number.parseInt(matchFrac[2], 10); + } else { + aspectRatioFloatValue = Number.parseFloat(maxAspectRatio); + } + } catch (e) { + console.error(e); + console.error(`Error when parsing '${maxAspectRatio}', fallback maxAspectRatio to default value`); + aspectRatioFloatValue = 2.4; + } + + let apiLevel = this.params.platformParams.apiLevel; + if (apiLevel === undefined) apiLevel = DefaultAPILevel; + + + if (apiLevel >= 26) { // Android 8.0 + const activities = data.manifest.application[0].activity; + for (const activity of activities) { + activity.$['android:maxAspectRatio'] = `${aspectRatioFloatValue}`; + } + } else { + const application = data.manifest.application[0]; + //append meta-data + application['meta-data'].push({ + $: { 'android:name': 'android.max_aspect', 'android:value': `${aspectRatioFloatValue}` } + }); + } + + }; + const fnUpdateCategory = (data: any) => { + if (!this.params.platformParams.androidInstant) { + console.log('android instant not configured'); + return; + } + const url = this.params.platformParams.remoteUrl; + if (!url) { + return; + } + const urlInfo = URL.parse(url); + if (!urlInfo.host) { + throw new Error(`parse url ${url} fail`); + } + const intentFilter: any = data.manifest.application[0].activity[0]['intent-filter'][0]; + if (intentFilter) { + intentFilter.data ||= []; + intentFilter.data = intentFilter.data.concat([ + { $: { 'android:host': urlInfo.host, 'android:pathPattern': urlInfo.path, 'android:scheme': 'https' } }, + { $: { 'android:scheme': 'http' } } + ]); + } + } + + if (fs.existsSync(manifestPath)) { + const app = await fnParseXml(manifestPath); + await fnUpdateOrientation(app.data); + await fnUpdateResizeableActivity(app.data); + await fnUpdateMaxAspectRation(app.data); + await app.save(); + } + if (fs.existsSync(instantManifestPath)) { + const instant = await fnParseXml(instantManifestPath); + await fnUpdateOrientation(instant.data); + await fnUpdateResizeableActivity(instant.data); + await fnUpdateMaxAspectRation(instant.data); + await fnUpdateCategory(instant.data); + await instant.save(); + } + } + + protected async updateAndroidGradleValues() { + const options = this.params.platformParams; + // android-studio gradle.properties + console.log(`update settings.properties`); + await cchelper.replaceInFile([ + { reg: '^rootProject\\.name.*', text: `rootProject.name = "${this.params.projectName}"` }, + { reg: ':CocosGame', text: `:${this.params.projectName}` } + ], ps.join(this.paths.nativePrjDir, 'settings.gradle')); + + console.log(`update gradle.properties`); + const gradlePropertyPath = cchelper.join(this.paths.nativePrjDir, 'gradle.properties'); + if (fs.existsSync(gradlePropertyPath)) { + let keystorePath = options.keystorePath; + if (process.platform === 'win32') { + keystorePath = cchelper.fixPath(keystorePath); + } + let apiLevel = options.apiLevel; + if (!apiLevel) { + apiLevel = DefaultAPILevel; + } + console.log(`AndroidAPI level ${apiLevel}`); + let content = fs.readFileSync(gradlePropertyPath, 'utf-8'); + if (keystorePath) { + content = content.replace(/.*RELEASE_STORE_FILE=.*/, `RELEASE_STORE_FILE=${keystorePath}`); + content = content.replace(/.*RELEASE_STORE_PASSWORD=.*/, `RELEASE_STORE_PASSWORD=${options.keystorePassword}`); + content = content.replace(/.*RELEASE_KEY_ALIAS=.*/, `RELEASE_KEY_ALIAS=${options.keystoreAlias}`); + content = content.replace(/.*RELEASE_KEY_PASSWORD=.*/, `RELEASE_KEY_PASSWORD=${options.keystoreAliasPassword}`); + } else { + content = content.replace(/.*RELEASE_STORE_FILE=.*/, `# RELEASE_STORE_FILE=${keystorePath}`); + content = content.replace(/.*RELEASE_STORE_PASSWORD=.*/, `# RELEASE_STORE_PASSWORD=${options.keystorePassword}`); + content = content.replace(/.*RELEASE_KEY_ALIAS=.*/, `# RELEASE_KEY_ALIAS=${options.keystoreAlias}`); + content = content.replace(/.*RELEASE_KEY_PASSWORD=.*/, `# RELEASE_KEY_PASSWORD=${options.keystoreAliasPassword}`); + } + + const compileSDKVersion = this.parseVersion(content, 'PROP_COMPILE_SDK_VERSION', 27); + const minimalSDKVersion = this.parseVersion(content, 'PROP_MIN_SDK_VERSION', 21); + + content = content.replace(/PROP_TARGET_SDK_VERSION=.*/, `PROP_TARGET_SDK_VERSION=${apiLevel}`); + content = content.replace(/PROP_COMPILE_SDK_VERSION=.*/, `PROP_COMPILE_SDK_VERSION=${Math.max(apiLevel, compileSDKVersion, 27)}`); + content = content.replace(/PROP_MIN_SDK_VERSION=.*/, `PROP_MIN_SDK_VERSION=${Math.min(apiLevel, minimalSDKVersion)}`); + content = content.replace(/PROP_APP_NAME=.*/, `PROP_APP_NAME=${this.params.projectName}`); + content = content.replace(/PROP_ENABLE_INSTANT_APP=.*/, `PROP_ENABLE_INSTANT_APP=${options.androidInstant ? "true" : "false"}`); + content = content.replace(/PROP_ENABLE_INPUTSDK=.*/, `PROP_ENABLE_INPUTSDK=${options.inputSDK ? "true" : "false"}`); + content = content.replace(/PROP_IS_DEBUG=.*/, `PROP_IS_DEBUG=${this.params.debug ? "true" : "false"}`); + + content = content.replace(/RES_PATH=.*/, `RES_PATH=${cchelper.fixPath(this.paths.buildDir)}`); + content = content.replace(/COCOS_ENGINE_PATH=.*/, `COCOS_ENGINE_PATH=${cchelper.fixPath(Paths.nativeRoot)}`); + content = content.replace(/APPLICATION_ID=.*/, `APPLICATION_ID=${options.packageName}`); + content = content.replace(/NATIVE_DIR=.*/, `NATIVE_DIR=${cchelper.fixPath(this.paths.platformTemplateDirInPrj)}`); + + + if (process.platform === 'win32') { + options.ndkPath = options.ndkPath.replace(/\\/g, '\\\\'); + } + + content = content.replace(/PROP_NDK_PATH=.*/, `PROP_NDK_PATH=${options.ndkPath}`); + + const abis = (options.appABIs && options.appABIs.length > 0) ? options.appABIs.join(':') : 'armeabi-v7a'; + // todo:新的template里面有个注释也是这个字段,所以要加个g + content = content.replace(/PROP_APP_ABI=.*/g, `PROP_APP_ABI=${abis}`); + fs.writeFileSync(gradlePropertyPath, content); + + // generate local.properties + content = ''; + content += `sdk.dir=${options.sdkPath}`; + // windows 需要使用的这样的格式 e\:\\aa\\bb\\cc + if (process.platform === 'win32') { + content = content.replace(/\\/g, '\\\\'); + content = content.replace(/:/g, '\\:'); + } + + fs.writeFileSync(cchelper.join(ps.dirname(gradlePropertyPath), 'local.properties'), content, { encoding: 'utf8' }); + } else { + console.log(`warning: ${gradlePropertyPath} not found!`); + } + } + + /** + * Deprecated, only be compatible with historical packaging tools + */ + protected async configAndroidInstant() { + if (!this.params.platformParams.androidInstant) { + console.log('android instant not configured'); + return; + } + const url = this.params.platformParams.remoteUrl; + if (!url) { + return; + } + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + if (!fs.existsSync(manifestPath)) { + throw new Error(`${manifestPath} not found`); + } + const urlInfo = URL.parse(url); + if (!urlInfo.host) { + throw new Error(`parse url ${url} fail`); + } + let manifest = fs.readFileSync(manifestPath, 'utf8'); + manifest = manifest.replace(//, (str) => { + let newStr = ''; + newStr += `\n ` + + `\n `; + return newStr; + }); + + fs.writeFileSync(manifestPath, manifest, 'utf8'); + } + + private async generateAppNameValues() { + const valuesPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'res/values/strings.xml'); + const matchCnt = fs.readFileSync(valuesPath, 'utf8').toString().split('\n').map(x => x.trim()).filter(x => /name=\"app_name\"/.test(x)).length; + if (matchCnt == 0) { // should generate + const content = [ + ``, + ` ${this.params.projectName}`, + ``, + ]; + const dir = ps.join(this.paths.buildDir, 'proj/res/values'); + await fs.ensureDir(dir); + await fs.writeFileSync(ps.join(dir, `strings.xml`), content.join('\n'), 'utf8'); + } + } + + /** + * 到对应目录拷贝文件到工程发布目录 + */ + async copyToDist(): Promise { + const options = this.params.platformParams; + + const suffix = this.params.debug ? 'debug' : 'release'; + const destDir: string = ps.join(this.paths.buildDir, 'publish', suffix); + fs.ensureDirSync(destDir); + let apkName = `${this.projectNameASCII()}-${suffix}.apk`; + let apkPath = ps.join(this.outputsDir(), `apk/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + if (options.androidInstant) { + apkName = `instantapp-${suffix}.apk`; + apkPath = ps.join(this.paths.nativePrjDir, `build/instantapp/outputs/apk/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`instant apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + } + + if (options.appBundle) { + apkName = `${this.params.projectName}-${suffix}.aab`; + apkPath = ps.join(this.outputsDir(), `bundle/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`instant apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + } + return true; + } + + // ---------------------------- run ------------------------- // + + async run() { + if (await this.install()) { + return await this.startApp(); + } + return true; + } + + getAdbPath() { + return ps.join( + this.params.platformParams.sdkPath, + `platform-tools/adb${process.platform === 'win32' ? '.exe' : ''}`); + } + + getApkPath() { + const suffix = this.params.debug ? 'debug' : 'release'; + const apkName = `${this.projectNameASCII()}-${suffix}.apk`; + return ps.join(this.outputsDir(), `apk/${suffix}/${apkName}`); + } + + private outputsDir() { + const folderName = this.projectNameASCII(); + const targetDir = ps.join(this.paths.nativePrjDir, 'build', folderName); + const fallbackDir = ps.join(this.paths.nativePrjDir, 'build', this.params.projectName); + return ps.join(fs.existsSync(targetDir) ? targetDir : fallbackDir, 'outputs'); + } + + async install(): Promise { + const apkPath = this.getApkPath(); + const adbPath = this.getAdbPath(); + + if (!fs.existsSync(apkPath)) { + throw new Error(`can not find apk at ${apkPath}`); + } + + if (!fs.existsSync(adbPath)) { + throw new Error(`can not find adb at ${adbPath}`); + } + + if (!this.checkConnectedDevices(adbPath)) { + console.error(`can not find any connected devices, please connect you device or start an Android emulator`); + } + + if (await this.checkApkInstalled(adbPath)) { + await cchelper.runCmd( + adbPath, ['uninstall', this.params.platformParams.packageName], false); + } + + await cchelper.runCmd(adbPath, ['install', '-r', apkPath], false); + return true; + } + + checkConnectedDevices(adbPath: string): boolean { + const cp = spawnSync(adbPath, ['devices'], { shell: true, env: process.env, cwd: process.cwd() }); + if (cp.stderr && cp.stderr.length > 0) { + console.log(`[adb devices] stderr: ${cp.stderr.toString('utf8')}`); + } + if (cp.error) { + console.log(`[adb devices] error: ${cp.error}`); + } + if (cp.output.length > 1) { + for (const chunk of cp.output) { + if (chunk) { + const chunkAny: any = chunk; + const chuckStr: string = typeof chunk === 'string' ? chunk : (chunkAny.buffer && chunkAny.buffer instanceof ArrayBuffer ? chunkAny.toString() : chunkAny.toString()); + const lines = chuckStr.split('\n'); + for (let line of lines) { + if (/^[0-9a-zA-Z]+\s+\w+/.test(line)) { + return true; // device connected + } + } + } + } + } + return false; + } + + async checkApkInstalled(adbPath: string) { + const ret: string = await new Promise((resolve, reject) => { + const cp = spawn( + adbPath, + [ + 'shell pm list packages | grep', + this.params.platformParams.packageName, + ], + { + shell: true, + env: process.env, + cwd: process.cwd(), + }); + cp.stdout.on(`data`, (chunk) => { + resolve(chunk.toString()); + }); + cp.stderr.on(`data`, (chunk) => { + resolve(''); + }); + cp.on('close', (code, signal) => { + resolve(''); + }); + }); + return ret.includes(this.params.platformParams.packageName); + } + + async startApp(): Promise { + const adbPath = this.getAdbPath(); + await cchelper.runCmd( + adbPath, + [ + 'shell', 'am', 'start', '-n', + `${this.params.platformParams.packageName}/com.cocos.game.AppActivity`, + ], + false); + return true; + } +} diff --git a/vender/google/play/billing/billing.ts b/vender/google/play/billing/billing.ts deleted file mode 100644 index 7006dd9eb7f..00000000000 --- a/vender/google/play/billing/billing.ts +++ /dev/null @@ -1,851 +0,0 @@ -/**************************************************************************** - Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*****************************************************************************/ -import { JSB } from 'internal:constants'; -import { native } from '../../../../cocos/native-binding'; -import { EventTarget } from '../../../../cocos/core/event'; - -//declare const jsb: any; - -export namespace googleplay { - /** - * @en Google Play Billing event type - * @zh Google Play Billing事件类型 - */ - export enum BillingEventType { - /** - * @en - * Called to notify that setup is complete. - * - * @zh - * 当安装已经完成时触发。 - */ - BILLING_SETUP_FINISHED = 'billing_setup_finished', - /** - * @en - * Called to notify that the connection to the billing service was lost. - * - * @zh - * 当Billing服务连接断开时触发。 - */ - BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', - /** - * @en - * Listen to this event to get notifications of purchase updates. - * - * @zh - * 监听这个事件可以获取购买更新。 - */ - PURCHASES_UPDATED = 'purchases_updated', - /** - * @en - * Called to notify that query product details operation has finished. - * - * @zh - * 查询产品详细信息操作完成时触发。 - */ - PRODUCT_DETAILS_RESPONSE = 'product_details_response', - /** - * @en - * Called to notify that the query purchases operation has finished. - * - * @zh - * 查询购买操作完成时触发。 - */ - QUERY_PURCHASES_RESPONSE = 'query_purchases_response', - /** - * @en - * Called to notify that a consume operation has finished. - * - * @zh - * 消费操作完成时触发。 - */ - CONSUME_RESPONSE = 'consume_response', - /** - * @en - * Called to notify that an acknowledge purchase operation has finished. - * - * @zh - * 确认购买操作完成时触发。 - */ - ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', - /** - * @en - * Called to notify when the get billing config flow has finished. - * - * @zh - * 获取Billing配置流程完成时触发。 - */ - BILLING_CONFIG_RESPONSE = 'billing_config_response', - /** - * @en - * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. - * - * @zh - * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 - */ - ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', - /** - * @en - * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. - * - * @zh - * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 - */ - EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', - /** - * @en - * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. - * - * @zh - * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 - */ - ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', - /** - * @en - * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. - * - * @zh - * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 - */ - EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', - /** - * @en - * Called to notify that the alternative billing only dialog flow is finished. - * - * @zh - * 当仅替代Billing对话流程已完成时触发。 - */ - ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', - /** - * @en - * Called to notify that the external offer information dialog flow is finished. - * - * @zh - * 当外部报价信息对话流程已完成时触发。 - */ - EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', - /** - * @en - * Called to notify when the in-app messaging flow has finished. - * - * @zh - * 当应用内消息流程完成时触发。 - */ - IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', - - } - - /** - * @en - * Supported Product types. - * - * @zh - * 支持的产品类型。 - */ - export enum ProductType { - /** - * @en - * A Product type for Android apps in-app products. - * - * @zh - * Android 应用内产品的产品类型。 - */ - INAPP = 'inapp', - /** - * @en - * A Product type for Android apps subscriptions. - * - * @zh - * Android 应用程序订阅的产品类型。 - */ - SUBS = 'subs' - } - - /** - * @en - * Possible response codes. - * - * @zh - * 可能的响应代码。 - */ - export enum BillingResponseCode { - /** - * @en - * This field is deprecated. - * See SERVICE_UNAVAILABLE which will be used instead of this code. - * - * @zh - * 这个字段已经废弃。 - * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 - */ - SERVICE_TIMEOUT = -3, - /** - * @en - * The requested feature is not supported by the Play Store on the current device. - * - * @zh - * 当前设备上的 Play Store 不支持所请求的功能。 - */ - FEATURE_NOT_SUPPORTED = -2, - /** - * @en - * The app is not connected to the Play Store service via the Google Play Billing Library. - * - * @zh - * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 - */ - SERVICE_DISCONNECTED = -1, - /** - * @en - * Success. - * - * @zh - * 成功。 - */ - OK = 0, - /** - * @en - * Transaction was canceled by the user. - * - * @zh - * 交易已被用户取消。 - */ - USER_CANCELED = 1, - /** - * @en - * The service is currently unavailable. - * - * @zh - * 当前设备上的 Play Store 不支持所请求的功能。 - */ - SERVICE_UNAVAILABLE = 2, - /** - * @en - * A user billing error occurred during processing. - * - * @zh - * 处理过程中出现用户billing错误。 - */ - BILLING_UNAVAILABLE = 3, - /** - * @en - * The requested product is not available for purchase. - * - * @zh - * 所请求的产品无法购买。 - */ - ITEM_UNAVAILABLE = 4, - /** - * @en - * Error resulting from incorrect usage of the API. - * - * @zh - * 由于错误使用 API 而导致的错误。 - */ - DEVELOPER_ERROR = 5, - /** - * @en - * Fatal error during the API action. - * - * @zh - * API 操作期间发生致命错误。 - */ - ERROR = 6, - /** - * @en - * The purchase failed because the item is already owned. - * - * @zh - * 购买失败,因为该物品已被拥有。 - */ - ITEM_ALREADY_OWNED = 7, - /** - * @en - * Requested action on the item failed since it is not owned by the user. - * - * @zh - * 由于该项目不属于用户,因此对该项目请求的操作失败。 - */ - ITEM_NOT_OWNED = 8, - /** - * @en - * A network error occurred during the operation. - * - * @zh - * 操作期间发生网络错误。 - */ - NETWORK_ERROR = 12, - } - - /** - * @en - * Recurrence mode of the pricing phase. - * - * @zh - * 定价阶段的复现模式。 - */ - export enum RecurrenceMode { - /** - * @en - * The billing plan payment recurs for infinite billing periods unless cancelled. - * - * @zh - * 除非取消,否则billing计划付款将无限期地重复。 - */ - INFINITE_RECURRING = 1, - /** - * @en - * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. - * - * @zh - * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 - */ - FINITE_RECURRING = 2, - /** - * @en - * The billing plan payment is a one time charge that does not repeat. - * - * @zh - * Billing计划付款是一次性费用,不会重复。 - */ - NON_RECURRING = 3, - } - - /** - * @en - * Connection state of billing client. - * - * @zh - * Billing client的连接状态 - */ - export enum ConnectionState { - /** - * @en - * This client was not yet connected to billing service or was already closed. - * - * @zh - * 此客户端尚未连接到Billing服务或已关闭。 - */ - DISCONNECTED = 0, - /** - * @en - * This client is currently in process of connecting to billing service. - * - * @zh - * 此客户端目前正在连接到Billing服务。 - */ - CONNECTING = 1, - /** - * @en - * This client is currently connected to billing service. - * - * @zh - * 此客户端当前已连接到Billing服务。 - */ - CONNECTED = 2, - /** - * @en - * This client was already closed and shouldn't be used again. - * - * @zh - * 该客户端已关闭,不应再次使用。 - */ - CLOSED = 3, - } - - /** - * @en - * Features/capabilities supported by isFeatureSupported. - * - * @zh - * 支持的特性/能力isFeatureSupported。 - */ - export enum FeatureType { - /** - * @en - * Alternative billing only. - * - * @zh - * 仅限替代Billing。 - */ - ALTERNATIVE_BILLING_ONLY = 'jjj', - /** - * @en - * Get billing config. - * - * @zh - * 获取计费配置。 - */ - BILLING_CONFIG = 'ggg', - /** - * @en - * Play billing library support for external offer. - * - * @zh - * Play billing库支持外部报价。 - */ - EXTERNAL_OFFER = 'kkk', - /** - * @en - * Show in-app messages. - * - * @zh - * 显示应用内消息。 - */ - IN_APP_MESSAGING = 'bbb', - /** - * @en - * Launch a price change confirmation flow. - * - * @zh - * 启动价格变动确认流程。 - */ - PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', - /** - * @en - * Play billing library support for querying and purchasing. - * - * @zh - * Play Billing库支持查询、购买。 - */ - PRODUCT_DETAILS = 'fff', - /** - * @en - * Purchase/query for subscriptions. - * - * @zh - * 购买/查询订阅。 - */ - SUBSCRIPTIONS = 'subscriptions', - /** - * @en - * Subscriptions update/replace. - * - * @zh - * 订阅更新/替换。 - */ - UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', - } - - /** - * @en - * Possible purchase states. - * - * @zh - * 可能的购买状态。 - */ - export enum PurchaseState { - /** - * @en - * Purchase is pending and not yet completed to be processed by your app. - * - * @zh - * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 - */ - PENDING = 2, - /** - * @en - * Purchase is completed.. - * - * @zh - * 购买完成。 - */ - PURCHASED = 1, - /** - * @en - * Purchase with unknown state. - * - * @zh - * 未知状态 - */ - UNSPECIFIED_STATE = 0, - } - - /** - * @en - * Possible response codes. - * - * @zh - * InAppMessage可能的影响代码。 - */ - export enum InAppMessageResponseCode { - /** - * @en - * The flow has finished and there is no action needed from developers. - * - * @zh - * 流程已完成,开发人员无需采取任何行动。 - */ - NO_ACTION_NEEDED = 0, - /** - * @en - * The subscription status changed. - * - * @zh - * 订阅状态已改变。 - */ - SUBSCRIPTION_STATUS_UPDATED = 1, - } - - export type BillingResult = jsb.BillingResult; - export type OneTimePurchaseOfferDetails = jsb.OneTimePurchaseOfferDetails; - export type InstallmentPlanDetails = jsb.InstallmentPlanDetails; - export type PricingPhase = jsb.PricingPhase; - export type SubscriptionOfferDetails = jsb.SubscriptionOfferDetails; - export type ProductDetails = jsb.ProductDetails; - export type AccountIdentifiers = jsb.AccountIdentifiers; - export type PendingPurchaseUpdate = jsb.PendingPurchaseUpdate; - export type Purchase = jsb.Purchase; - export type BillingConfig = jsb.BillingConfig; - export type AlternativeBillingOnlyReportingDetails = jsb.AlternativeBillingOnlyReportingDetails; - export type ExternalOfferReportingDetails = jsb.ExternalOfferReportingDetails; - export type InAppMessageResult = jsb.InAppMessageResult; - - /** - * @en - * Interface for Google Play blling module. - * - * @zh - * Google Play blling模块的接口。 - * - */ - export const billing = new GooglePlayBilling(); -} - -interface BillingEventMap { - [googleplay.BillingEventType.BILLING_SETUP_FINISHED]: (result: googleplay.BillingResult) => void, - [googleplay.BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, - [googleplay.BillingEventType.PRODUCT_DETAILS_RESPONSE]: - (result: googleplay.BillingResult, productDetailsList: googleplay.ProductDetails[]) => void, - [googleplay.BillingEventType.PURCHASES_UPDATED]: (result: googleplay.BillingResult, purchases: googleplay.Purchase[]) => void, - [googleplay.BillingEventType.CONSUME_RESPONSE]: (result: googleplay.BillingResult, purchaseToken: string) => void, - [googleplay.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE]: (result: googleplay.BillingResult) => void - [googleplay.BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: googleplay.BillingResult, purchases: googleplay.Purchase[]) => void, - [googleplay.BillingEventType.BILLING_CONFIG_RESPONSE]: (result: googleplay.BillingResult, config: googleplay.BillingConfig) => void - [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: - (result: googleplay.BillingResult, alternativeBillingOnlyReportingDetails: googleplay.AlternativeBillingOnlyReportingDetails) => void - [googleplay.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: - (result: googleplay.BillingResult, externalOfferReportingDetails: googleplay.ExternalOfferReportingDetails) => void - [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: googleplay.BillingResult) => void - [googleplay.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: googleplay.BillingResult) => void - [googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: googleplay.BillingResult) => void - [googleplay.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: googleplay.BillingResult) => void - [googleplay.BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: googleplay.InAppMessageResult) => void -} - -class GooglePlayBilling { - private _eventTarget: EventTarget = new EventTarget(); - constructor () { - if (!JSB || !jsb.billing) { - return; - } - jsb.onBillingSetupFinished = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.BILLING_SETUP_FINISHED, result); - }; - - jsb.onBillingServiceDisconnected = (): void => { - this._eventTarget.emit(googleplay.BillingEventType.BILLING_SERVICE_DISCONNECTED); - }; - - jsb.onProductDetailsResponse = ( - result: googleplay.BillingResult, - productDetailsList: googleplay.ProductDetails[], - ): void => { - this._eventTarget.emit(googleplay.BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); - }; - - jsb.onPurchasesUpdated = ( - result: googleplay.BillingResult, - purchaseList: googleplay.Purchase[], - ): void => { - this._eventTarget.emit(googleplay.BillingEventType.PURCHASES_UPDATED, result, purchaseList); - }; - - jsb.onConsumeResponse = ( - result: googleplay.BillingResult, - purchaseToken: string, - ): void => { - this._eventTarget.emit(googleplay.BillingEventType.CONSUME_RESPONSE, result, purchaseToken); - }; - - jsb.onAcknowledgePurchaseResponse = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); - }; - - jsb.onQueryPurchasesResponse = ( - result: googleplay.BillingResult, - purchaseList: googleplay.Purchase[], - ): void => { - this._eventTarget.emit(googleplay.BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); - }; - - jsb.onBillingConfigResponse = (result: googleplay.BillingResult, config: googleplay.BillingConfig): void => { - this._eventTarget.emit(googleplay.BillingEventType.BILLING_CONFIG_RESPONSE, result, config); - }; - - jsb.onAlternativeBillingOnlyTokenResponse = (result: googleplay.BillingResult, details: googleplay.AlternativeBillingOnlyReportingDetails): void => { - this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); - }; - - jsb.onExternalOfferReportingDetailsResponse = (result: googleplay.BillingResult, details: googleplay.ExternalOfferReportingDetails): void => { - this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); - }; - - jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); - }; - - jsb.onExternalOfferAvailabilityResponse = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); - }; - - jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); - }; - - jsb.onExternalOfferInformationDialogResponse = (result: googleplay.BillingResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); - }; - - jsb.onInAppMessageResponse = (result: googleplay.InAppMessageResult): void => { - this._eventTarget.emit(googleplay.BillingEventType.IN_APP_MESSAGE_RESPONSE, result); - }; - } - - /** - * @en Starts up BillingClient setup process asynchronously. - * @zh 异步启动 BillingClient 设置过程。 - */ - public startConnection (): void { - jsb.billing?.startConnection(); - } - - /** - * @en Closes the connection and releases all held resources such as service connections. - * @zh 关闭连接并释放所有持有的资源,例如服务连接。 - */ - public endConnection (): void { - jsb.billing?.endConnection(); - } - - /** - * @en Get the current billing client connection state. - * @zh 获取当前billing客户端连接状态。 - */ - public getConnectionState (): number { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.getConnectionState(); - } - return googleplay.ConnectionState.DISCONNECTED; - } - - /** - * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. - Returns true if the client is currently connected to the service, false otherwise. - * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 - 如果客户端当前已连接到服务,则返回 true,否则返回 false。 - */ - public isReady (): boolean { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isReady(); - } - return false; - } - - /** - * @en Performs a network query the details of products available for sale in your app. - * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 - * @param productId @zh 产品ID。 @en product id. - * @param productType @zh 产品类型。 @en product type. - * - */ - public queryProductDetailsParams (productId: string[] | string, productType: googleplay.ProductType): void { - if (productId instanceof Array) { - jsb.billing?.queryProductDetailsParams(productId, productType); - } else { - const productIdList: string[] = []; - productIdList.push(productId); - jsb.billing?.queryProductDetailsParams(productIdList, productType); - } - } - - /** - * @en Initiates the billing flow for an in-app purchase or subscription. - * @zh 启动应用内购买或订阅的计费流程。 - * @param productDetails @zh 产品详情。 @en product details. - * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. - */ - public launchBillingFlow (productDetails: googleplay.ProductDetails[] | googleplay.ProductDetails, selectedOfferToken: string | null): void { - if (productDetails instanceof Array) { - jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); - } else { - const productDetailsList: googleplay.ProductDetails[] = []; - productDetailsList.push(productDetails); - jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); - } - } - - /** - * @en Consumes a given in-app product. - * @zh 消费指定的应用内产品。 - * @param purchase @zh 已经购买的产品。 @en Purchased Products. - */ - public consumePurchases (purchase: googleplay.Purchase[] | googleplay.Purchase): void { - if (purchase instanceof Array) { - jsb.billing?.consumePurchases(purchase); - } else { - const purchaseList: googleplay.Purchase[] = []; - purchaseList.push(purchase); - jsb.billing?.consumePurchases(purchaseList); - } - } - - /** - * @en Acknowledges in-app purchases. - * @zh 确认应用内购买。 - * @param purchase @zh 已经购买的产品。 @en Purchased Products. - */ - public acknowledgePurchase (purchase: googleplay.Purchase[] | googleplay.Purchase): void { - if (purchase instanceof Array) { - jsb.billing?.acknowledgePurchase(purchase); - } else { - const purchaseList: googleplay.Purchase[] = []; - purchaseList.push(purchase); - jsb.billing?.acknowledgePurchase(purchaseList); - } - } - - /** - * @en Returns purchases details for currently owned items bought within your app. - * @zh 返回您应用内当前拥有的购买商品的购买详情。 - * @param productType @zh 产品类型 @en Product type. - */ - public queryPurchasesAsync (productType: googleplay.ProductType): void { - jsb.billing?.queryPurchasesAsync(productType); - } - - /** - * @en Gets the billing config, which stores configuration used to perform billing operations. - * @zh 获取计费配置,其中存储用于执行计费操作的配置。 - */ - public getBillingConfigAsync (): void { - jsb.billing?.getBillingConfigAsync(); - } - - /** - * @en Creates alternative billing only purchase details that can be used to report a transaction made - * via alternative billing without user choice to use Google Play billing. - * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 - */ - public createAlternativeBillingOnlyReportingDetailsAsync (): void { - jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); - } - - /** - * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. - * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 - */ - public isAlternativeBillingOnlyAvailableAsync (): void { - jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); - } - - /** - * @en Creates purchase details that can be used to report a transaction made via external offer. - * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 - */ - public createExternalOfferReportingDetailsAsync (): void { - jsb.billing?.createExternalOfferReportingDetailsAsync(); - } - - /** - * @en Checks the availability of providing external offer. - * @zh 检查提供外部报价的可用性。 - */ - public isExternalOfferAvailableAsync (): void { - jsb.billing?.isExternalOfferAvailableAsync(); - } - - /** - * @en Checks if the specified feature or capability is supported by the Play Store. - * @zh 检查 Play Store 是否支持指定的功能。 - * @param feature @zh 功能特性 @en feature. - */ - public isFeatureSupported (feature: string): googleplay.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isFeatureSupported(feature); - } - return null; - } - - /** - * @en Shows the alternative billing only information dialog on top of the calling app. - * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 - */ - public showAlternativeBillingOnlyInformationDialog (): googleplay.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showAlternativeBillingOnlyInformationDialog(); - } - return null; - } - - /** - * @en Shows the external offer information dialog on top of the calling app. - * @zh 在调用应用程序顶部显示外部优惠信息对话框。 - */ - public showExternalOfferInformationDialog (): googleplay.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showExternalOfferInformationDialog(); - } - return null; - } - - /** - * @en Overlays billing related messages on top of the calling app. - * @zh 在调用应用程序上叠加与计费相关的消息。 - */ - public showInAppMessages (): googleplay.BillingResult | null { - if (jsb.billing) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showInAppMessages(); - } - return null; - } - - public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { - this._eventTarget.on(type, callback, target); - return callback; - } - public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { - this._eventTarget.once(type, callback, target); - return callback; - } - public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { - this._eventTarget.off(eventType, callback, target); - } -} diff --git a/vendor/google/play/index.ts b/vendor/google/play/index.ts new file mode 100644 index 00000000000..51359d6f468 --- /dev/null +++ b/vendor/google/play/index.ts @@ -0,0 +1,25 @@ +/* + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +export * from './billing/billing'; From e0169062eeb8edd0f528b073123e23826640ca67 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Thu, 28 Nov 2024 14:52:47 +0800 Subject: [PATCH 09/13] =?UTF-8?q?1=E3=80=81Name=20change=20from=20billing?= =?UTF-8?q?=20to=20GoogleBilling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @types/jsb.d.ts | 2 +- editor/engine-features/render-config.json | 2 +- exports/vendor.ts | 2 +- native/CMakeLists.txt | 22 +-- ...jsb_billing.cpp => jsb_google_billing.cpp} | 164 +++++++++--------- .../bindings/manual/jsb_module_register.cpp | 2 +- .../billing/GoogleBilling.java} | 38 ++-- .../billing/GoogleBillingHelper.java} | 12 +- .../platform/android/libcocos2dx/build.gradle | 4 +- .../modules/google_play/billing/billing.cpp | 116 ------------- .../platform/java/jni/JniCocosBilling.cpp | 112 ------------ .../java/jni/JniGoogleBillingHandler.cpp | 112 ++++++++++++ .../vendor/google/billing/GoogleBilling.cpp | 116 +++++++++++++ .../google/billing/GoogleBilling.h} | 8 +- .../google/billing/GoogleBillingHelper.cpp} | 152 ++++++++-------- .../google/billing/GoogleBillingHelper.h} | 2 +- .../source/platforms/google-play.ts | 42 +++++ templates/google-play/build/gradle.properties | 2 +- vendor/google/{play => }/billing/billing.ts | 60 +++---- vendor/google/{play => }/index.ts | 0 20 files changed, 506 insertions(+), 464 deletions(-) rename native/cocos/bindings/manual/{jsb_billing.cpp => jsb_google_billing.cpp} (89%) rename native/cocos/platform/android/java/vendor/{com/cocos/billing/CocosBilling.java => google/billing/GoogleBilling.java} (91%) rename native/cocos/platform/android/java/vendor/{com/cocos/billing/CocosBillingHelper.java => google/billing/GoogleBillingHelper.java} (97%) delete mode 100644 native/cocos/platform/android/modules/google_play/billing/billing.cpp delete mode 100644 native/cocos/platform/java/jni/JniCocosBilling.cpp create mode 100644 native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp create mode 100644 native/vendor/google/billing/GoogleBilling.cpp rename native/{cocos/platform/android/modules/google_play/billing/billing.h => vendor/google/billing/GoogleBilling.h} (94%) rename native/{cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp => vendor/google/billing/GoogleBillingHelper.cpp} (76%) rename native/{cocos/platform/android/modules/google_play/billing/JniBillingHelper.h => vendor/google/billing/GoogleBillingHelper.h} (99%) rename vendor/google/{play => }/billing/billing.ts (91%) rename vendor/google/{play => }/index.ts (100%) diff --git a/@types/jsb.d.ts b/@types/jsb.d.ts index 11262fdd1e6..418b3ec8394 100644 --- a/@types/jsb.d.ts +++ b/@types/jsb.d.ts @@ -717,7 +717,7 @@ declare namespace jsb { * @en Main interface for communication between the Google Play library and user application code. * @zh 产品 ID。 */ - const billing: { + const googleBilling: { /** * @en Starts up BillingClient setup process asynchronously. * @zh 异步启动 BillingClient 设置过程。 diff --git a/editor/engine-features/render-config.json b/editor/engine-features/render-config.json index 727b933afc3..d121ba7a9d5 100644 --- a/editor/engine-features/render-config.json +++ b/editor/engine-features/render-config.json @@ -318,7 +318,7 @@ "description": "i18n:ENGINE.features.vendor.description", "enginePlugin": false, "isNativeModule": true, - "cmakeConfig": "USE_GOOGLE_PLAY_BILLING", + "cmakeConfig": "USE_GOOGLE_BILLING", "hidden": true }, "spine": { diff --git a/exports/vendor.ts b/exports/vendor.ts index e9aea6db849..e6ca3a50a09 100644 --- a/exports/vendor.ts +++ b/exports/vendor.ts @@ -23,4 +23,4 @@ THE SOFTWARE. */ -export * from '../vendor/google/play'; +export * from '../vendor/google'; diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 870e9714e60..b001e9cdba7 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -48,7 +48,7 @@ cc_set_if_undefined(USE_WEBP ON) cc_set_if_undefined(NET_MODE 0) # 0 is client cc_set_if_undefined(USE_REMOTE_LOG OFF) cc_set_if_undefined(USE_ADPF OFF) -cc_set_if_undefined(USE_GOOGLE_PLAY_BILLING OFF) +cc_set_if_undefined(USE_GOOGLE_BILLING OFF) if(ANDROID AND NOT DEFINED USE_CCACHE) if("$ENV{COCOS_USE_CCACHE}" STREQUAL "1") @@ -760,12 +760,12 @@ elseif(ANDROID OR OHOS) endif() endif() -if(USE_GOOGLE_PLAY_BILLING) +if(USE_GOOGLE_BILLING) cocos_source_files( - cocos/platform/android/modules/google_play/billing/billing.cpp - cocos/platform/android/modules/google_play/billing/billing.h - cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp - cocos/platform/android/modules/google_play/billing/JniBillingHelper.h + vendor/google/billing/GoogleBilling.cpp + vendor/google/billing/GoogleBilling.h + vendor/google/billing/GoogleBillingHelper.cpp + vendor/google/billing/GoogleBillingHelper.h ) endif() @@ -1194,8 +1194,8 @@ elseif(LINUX) ) endif() -if(USE_GOOGLE_PLAY_BILLING) - list(APPEND CC_JNI_SRC_FILES ${CWD}/cocos/platform/java/jni/JniCocosBilling.cpp) +if(USE_GOOGLE_BILLING) + list(APPEND CC_JNI_SRC_FILES ${CWD}/cocos/platform/java/jni/JniGoogleBillingHandler.cpp) endif() ##### renderer @@ -2608,9 +2608,9 @@ if(USE_ADPF) ) endif() -if(USE_GOOGLE_PLAY_BILLING) +if(USE_GOOGLE_BILLING) cocos_source_files( - cocos/bindings/manual/jsb_billing.cpp + cocos/bindings/manual/jsb_google_billing.cpp ) endif() @@ -3147,7 +3147,7 @@ function(cc_apply_definations target) $<$:SCRIPT_ENGINE_TYPE=6> $<$,$>:CC_DEBUG=1> $,CC_USE_APDF=1,CC_USE_USE_APDF=0> - $,CC_USE_GOOGLE_PLAY_BILLING=1,CC_USE_GOOGLE_PLAY_BILLING=0> + $,CC_USE_GOOGLE_BILLING=1,CC_USE_GOOGLE_BILLING=0> ) endfunction() diff --git a/native/cocos/bindings/manual/jsb_billing.cpp b/native/cocos/bindings/manual/jsb_google_billing.cpp similarity index 89% rename from native/cocos/bindings/manual/jsb_billing.cpp rename to native/cocos/bindings/manual/jsb_google_billing.cpp index 6a772ccd39a..9321269fbb3 100644 --- a/native/cocos/bindings/manual/jsb_billing.cpp +++ b/native/cocos/bindings/manual/jsb_google_billing.cpp @@ -29,7 +29,7 @@ #include "bindings/jswrapper/SeApi.h" #include "bindings/manual/jsb_conversions.h" #include "bindings/manual/jsb_global.h" - #include "cocos/platform/android/modules/google_play/billing/billing.h" + #include "vendor/google/billing/GoogleBilling.h" JSB_REGISTER_OBJECT_TYPE(cc::BillingResult); JSB_REGISTER_OBJECT_TYPE(cc::OneTimePurchaseOfferDetails); JSB_REGISTER_OBJECT_TYPE(cc::InstallmentPlanDetails); @@ -40,7 +40,7 @@ JSB_REGISTER_OBJECT_TYPE(cc::AccountIdentifiers); JSB_REGISTER_OBJECT_TYPE(cc::PendingPurchaseUpdate); JSB_REGISTER_OBJECT_TYPE(cc::Purchase); JSB_REGISTER_OBJECT_TYPE(cc::BillingConfig); -JSB_REGISTER_OBJECT_TYPE(cc::Billing); +JSB_REGISTER_OBJECT_TYPE(cc::GoogleBilling); #endif @@ -1204,35 +1204,35 @@ bool js_register_cc_BillingConfig(se::Object* obj) { // NOLINT return true; } -SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Billing) +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Google_Billing) -static bool js_cc_Billing_startConnection(se::State& s) { // NOLINT - cc::Billing::getInstance().startConnection(); +static bool js_cc_Google_Billing_startConnection(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().startConnection(); return true; } -SE_BIND_FUNC(js_cc_Billing_startConnection) +SE_BIND_FUNC(js_cc_Google_Billing_startConnection) -static bool js_cc_Billing_endConnection(se::State& s) { // NOLINT - cc::Billing::getInstance().endConnection(); +static bool js_cc_Google_Billing_endConnection(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().endConnection(); return true; } -SE_BIND_FUNC(js_cc_Billing_endConnection) +SE_BIND_FUNC(js_cc_Google_Billing_endConnection) -static bool js_cc_Billing_getConnectionState(se::State& s) { // NOLINT - int connectionState = cc::Billing::getInstance().getConnectionState(); +static bool js_cc_Google_Billing_getConnectionState(se::State& s) { // NOLINT + int connectionState = cc::GoogleBilling::getInstance().getConnectionState(); s.rval().setFloat(connectionState); return true; } -SE_BIND_FUNC(js_cc_Billing_getConnectionState) +SE_BIND_FUNC(js_cc_Google_Billing_getConnectionState) -static bool js_cc_Billing_isReady(se::State& s) { - int isReady = cc::Billing::getInstance().isReady(); +static bool js_cc_Google_Billing_isReady(se::State& s) { + int isReady = cc::GoogleBilling::getInstance().isReady(); s.rval().setFloat(isReady); return true; } -SE_BIND_FUNC(js_cc_Billing_isReady) +SE_BIND_FUNC(js_cc_Google_Billing_isReady) -static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_queryProductDetailsParams(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1250,12 +1250,12 @@ static bool js_cc_Billing_queryProductDetailsParams(se::State& s) { // NOLINT SE_PRECONDITION2(ok, false, "Error processing arguments"); arg3 = &temp3; - cc::Billing::getInstance().queryProductDetailsParams((std::vector const&)*arg2, (std::string const&)*arg3); + cc::GoogleBilling::getInstance().queryProductDetailsParams((std::vector const&)*arg2, (std::string const&)*arg3); return true; } -SE_BIND_FUNC(js_cc_Billing_queryProductDetailsParams) +SE_BIND_FUNC(js_cc_Google_Billing_queryProductDetailsParams) -static bool js_cc_Billing_launchBillingFlow(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_launchBillingFlow(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1278,13 +1278,13 @@ static bool js_cc_Billing_launchBillingFlow(se::State& s) { // NOLINT SE_PRECONDITION2(ok, false, "Error processing arguments"); arg3 = &temp3; - cc::Billing::getInstance().launchBillingFlow((std::vector const&)*arg2, (std::string const&)*arg3); + cc::GoogleBilling::getInstance().launchBillingFlow((std::vector const&)*arg2, (std::string const&)*arg3); return true; } -SE_BIND_FUNC(js_cc_Billing_launchBillingFlow) +SE_BIND_FUNC(js_cc_Google_Billing_launchBillingFlow) -static bool js_cc_Billing_consumePurchases(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_consumePurchases(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1300,13 +1300,13 @@ static bool js_cc_Billing_consumePurchases(se::State& s) { // NOLINT SE_PRECONDITION2(ok, false, "Error processing arguments"); arg2 = &temp2; - cc::Billing::getInstance().consumePurchases((std::vector const&)*arg2); + cc::GoogleBilling::getInstance().consumePurchases((std::vector const&)*arg2); return true; } -SE_BIND_FUNC(js_cc_Billing_consumePurchases) +SE_BIND_FUNC(js_cc_Google_Billing_consumePurchases) -static bool js_cc_Billing_acknowledgePurchase(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_acknowledgePurchase(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1322,13 +1322,13 @@ static bool js_cc_Billing_acknowledgePurchase(se::State& s) { // NOLINT SE_PRECONDITION2(ok, false, "Error processing arguments"); arg1 = &temp2; - cc::Billing::getInstance().acknowledgePurchase((std::vector const&)*arg1); + cc::GoogleBilling::getInstance().acknowledgePurchase((std::vector const&)*arg1); return true; } -SE_BIND_FUNC(js_cc_Billing_acknowledgePurchase) +SE_BIND_FUNC(js_cc_Google_Billing_acknowledgePurchase) -static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_queryPurchasesAsync(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1344,43 +1344,43 @@ static bool js_cc_Billing_queryPurchasesAsync(se::State& s) { // NOLINT SE_PRECONDITION2(ok, false, "Error processing arguments"); arg2 = &temp2; - cc::Billing::getInstance().queryPurchasesAsync((std::string const&)*arg2); + cc::GoogleBilling::getInstance().queryPurchasesAsync((std::string const&)*arg2); return true; } -SE_BIND_FUNC(js_cc_Billing_queryPurchasesAsync) +SE_BIND_FUNC(js_cc_Google_Billing_queryPurchasesAsync) -static bool js_cc_Billing_getBillingConfigAsync(se::State& s) { // NOLINT - cc::Billing::getInstance().getBillingConfigAsync(); +static bool js_cc_Google_Billing_getBillingConfigAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().getBillingConfigAsync(); return true; } -SE_BIND_FUNC(js_cc_Billing_getBillingConfigAsync) +SE_BIND_FUNC(js_cc_Google_Billing_getBillingConfigAsync) -static bool js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { // NOLINT - cc::Billing::getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); +static bool js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); return true; } -SE_BIND_FUNC(js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync) +SE_BIND_FUNC(js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync) -static bool js_cc_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { // NOLINT - cc::Billing::getInstance().isAlternativeBillingOnlyAvailableAsync(); +static bool js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().isAlternativeBillingOnlyAvailableAsync(); return true; } -SE_BIND_FUNC(js_cc_Billing_isAlternativeBillingOnlyAvailableAsync) +SE_BIND_FUNC(js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync) -static bool js_cc_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { // NOLINT - cc::Billing::getInstance().createExternalOfferReportingDetailsAsync(); +static bool js_cc_Google_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().createExternalOfferReportingDetailsAsync(); return true; } -SE_BIND_FUNC(js_cc_Billing_createExternalOfferReportingDetailsAsync) +SE_BIND_FUNC(js_cc_Google_Billing_createExternalOfferReportingDetailsAsync) -static bool js_cc_Billing_isExternalOfferAvailableAsync(se::State& s) { // NOLINT - cc::Billing::getInstance().isExternalOfferAvailableAsync(); +static bool js_cc_Google_Billing_isExternalOfferAvailableAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().isExternalOfferAvailableAsync(); return true; } -SE_BIND_FUNC(js_cc_Billing_isExternalOfferAvailableAsync) +SE_BIND_FUNC(js_cc_Google_Billing_isExternalOfferAvailableAsync) -static bool js_cc_Billing_isFeatureSupported(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_isFeatureSupported(se::State& s) { // NOLINT CC_UNUSED bool ok = true; const auto& args = s.args(); size_t argc = args.size(); @@ -1394,74 +1394,74 @@ static bool js_cc_Billing_isFeatureSupported(se::State& s) { // NOLINT ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); SE_PRECONDITION2(ok, false, "Error processing arguments"); - cc::BillingResult* result = cc::Billing::getInstance().isFeatureSupported(temp2); + cc::BillingResult* result = cc::GoogleBilling::getInstance().isFeatureSupported(temp2); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); SE_PRECONDITION2(ok, false, "Error processing arguments"); return true; } -SE_BIND_FUNC(js_cc_Billing_isFeatureSupported) +SE_BIND_FUNC(js_cc_Google_Billing_isFeatureSupported) -static bool js_cc_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { // NOLINT CC_UNUSED bool ok = true; - cc::BillingResult* result = cc::Billing::getInstance().showAlternativeBillingOnlyInformationDialog(); + cc::BillingResult* result = cc::GoogleBilling::getInstance().showAlternativeBillingOnlyInformationDialog(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); SE_PRECONDITION2(ok, false, "Error processing arguments"); return true; } -SE_BIND_FUNC(js_cc_Billing_showAlternativeBillingOnlyInformationDialog) +SE_BIND_FUNC(js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog) -static bool js_cc_Billing_showExternalOfferInformationDialog(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_showExternalOfferInformationDialog(se::State& s) { // NOLINT CC_UNUSED bool ok = true; - cc::BillingResult* result = cc::Billing::getInstance().showExternalOfferInformationDialog(); + cc::BillingResult* result = cc::GoogleBilling::getInstance().showExternalOfferInformationDialog(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); SE_PRECONDITION2(ok, false, "Error processing arguments"); return true; } -SE_BIND_FUNC(js_cc_Billing_showExternalOfferInformationDialog) +SE_BIND_FUNC(js_cc_Google_Billing_showExternalOfferInformationDialog) -static bool js_cc_Billing_showInAppMessages(se::State& s) { // NOLINT +static bool js_cc_Google_Billing_showInAppMessages(se::State& s) { // NOLINT CC_UNUSED bool ok = true; - cc::BillingResult* result = cc::Billing::getInstance().showInAppMessages(); + cc::BillingResult* result = cc::GoogleBilling::getInstance().showInAppMessages(); ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); SE_PRECONDITION2(ok, false, "Error processing arguments"); return true; } -SE_BIND_FUNC(js_cc_Billing_showInAppMessages) +SE_BIND_FUNC(js_cc_Google_Billing_showInAppMessages) -static bool js_delete_cc_Billing(se::State& s) { // NOLINT +static bool js_delete_cc_Google_Billing(se::State& s) { // NOLINT return true; } -SE_BIND_FINALIZE_FUNC(js_delete_cc_Billing) +SE_BIND_FINALIZE_FUNC(js_delete_cc_Google_Billing) -bool js_register_cc_Billing(se::Object* obj) { // NOLINT - se::Value billingVal{se::Object::createPlainObject()}; - auto* cls = billingVal.toObject(); +bool js_register_cc_Google_Billing(se::Object* obj) { // NOLINT + se::Value googleBillingVal{se::Object::createPlainObject()}; + auto* cls = googleBillingVal.toObject(); - cls->defineFunction("startConnection", _SE(js_cc_Billing_startConnection)); - cls->defineFunction("endConnection", _SE(js_cc_Billing_endConnection)); - cls->defineFunction("getConnectionState", _SE(js_cc_Billing_getConnectionState)); - cls->defineFunction("isReady", _SE(js_cc_Billing_isReady)); - cls->defineFunction("queryProductDetailsParams", _SE(js_cc_Billing_queryProductDetailsParams)); + cls->defineFunction("startConnection", _SE(js_cc_Google_Billing_startConnection)); + cls->defineFunction("endConnection", _SE(js_cc_Google_Billing_endConnection)); + cls->defineFunction("getConnectionState", _SE(js_cc_Google_Billing_getConnectionState)); + cls->defineFunction("isReady", _SE(js_cc_Google_Billing_isReady)); + cls->defineFunction("queryProductDetailsParams", _SE(js_cc_Google_Billing_queryProductDetailsParams)); - cls->defineFunction("launchBillingFlow", _SE(js_cc_Billing_launchBillingFlow)); - cls->defineFunction("consumePurchases", _SE(js_cc_Billing_consumePurchases)); - cls->defineFunction("acknowledgePurchase", _SE(js_cc_Billing_acknowledgePurchase)); - cls->defineFunction("queryPurchasesAsync", _SE(js_cc_Billing_queryPurchasesAsync)); - cls->defineFunction("getBillingConfigAsync", _SE(js_cc_Billing_getBillingConfigAsync)); + cls->defineFunction("launchBillingFlow", _SE(js_cc_Google_Billing_launchBillingFlow)); + cls->defineFunction("consumePurchases", _SE(js_cc_Google_Billing_consumePurchases)); + cls->defineFunction("acknowledgePurchase", _SE(js_cc_Google_Billing_acknowledgePurchase)); + cls->defineFunction("queryPurchasesAsync", _SE(js_cc_Google_Billing_queryPurchasesAsync)); + cls->defineFunction("getBillingConfigAsync", _SE(js_cc_Google_Billing_getBillingConfigAsync)); - cls->defineFunction("createAlternativeBillingOnlyReportingDetailsAsync", _SE(js_cc_Billing_createAlternativeBillingOnlyReportingDetailsAsync)); - cls->defineFunction("isAlternativeBillingOnlyAvailableAsync", _SE(js_cc_Billing_isAlternativeBillingOnlyAvailableAsync)); - cls->defineFunction("createExternalOfferReportingDetailsAsync", _SE(js_cc_Billing_createExternalOfferReportingDetailsAsync)); - cls->defineFunction("isExternalOfferAvailableAsync", _SE(js_cc_Billing_isExternalOfferAvailableAsync)); + cls->defineFunction("createAlternativeBillingOnlyReportingDetailsAsync", _SE(js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync)); + cls->defineFunction("isAlternativeBillingOnlyAvailableAsync", _SE(js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync)); + cls->defineFunction("createExternalOfferReportingDetailsAsync", _SE(js_cc_Google_Billing_createExternalOfferReportingDetailsAsync)); + cls->defineFunction("isExternalOfferAvailableAsync", _SE(js_cc_Google_Billing_isExternalOfferAvailableAsync)); - cls->defineFunction("isFeatureSupported", _SE(js_cc_Billing_isFeatureSupported)); + cls->defineFunction("isFeatureSupported", _SE(js_cc_Google_Billing_isFeatureSupported)); - cls->defineFunction("showAlternativeBillingOnlyInformationDialog", _SE(js_cc_Billing_showAlternativeBillingOnlyInformationDialog)); - cls->defineFunction("showExternalOfferInformationDialog", _SE(js_cc_Billing_showExternalOfferInformationDialog)); - cls->defineFunction("showInAppMessages", _SE(js_cc_Billing_showInAppMessages)); + cls->defineFunction("showAlternativeBillingOnlyInformationDialog", _SE(js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog)); + cls->defineFunction("showExternalOfferInformationDialog", _SE(js_cc_Google_Billing_showExternalOfferInformationDialog)); + cls->defineFunction("showInAppMessages", _SE(js_cc_Google_Billing_showInAppMessages)); - obj->setProperty("billing", billingVal); + obj->setProperty("googleBilling", googleBillingVal); return true; } @@ -1487,7 +1487,7 @@ bool jsb_register_all_billing(se::Object* obj) { // NOLINT js_register_cc_PendingPurchaseUpdate(ns); js_register_cc_Purchase(ns); js_register_cc_BillingConfig(ns); - js_register_cc_Billing(ns); + js_register_cc_Google_Billing(ns); return true; } diff --git a/native/cocos/bindings/manual/jsb_module_register.cpp b/native/cocos/bindings/manual/jsb_module_register.cpp index 0074ff3ef80..0e0c7ee52ff 100644 --- a/native/cocos/bindings/manual/jsb_module_register.cpp +++ b/native/cocos/bindings/manual/jsb_module_register.cpp @@ -192,7 +192,7 @@ bool jsb_register_all_modules() { se->addRegisterCallback(register_all_socketio); #endif -#if CC_USE_GOOGLE_PLAY_BILLING +#if CC_USE_GOOGLE_BILLING se->addRegisterCallback(jsb_register_all_billing); #endif diff --git a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java b/native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java similarity index 91% rename from native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java rename to native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java index a4b821c8f8c..9cd1bc853d8 100644 --- a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBilling.java +++ b/native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java @@ -22,7 +22,7 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -package com.cocos.billing; +package google.billing; import android.util.Log; import androidx.annotation.NonNull; @@ -64,13 +64,13 @@ of this software and associated documentation files (the "Software"), to deal import java.util.List; import java.util.Map; -public class CocosBilling implements BillingClientStateListener, +public class GoogleBilling implements BillingClientStateListener, PurchasesUpdatedListener, ProductDetailsResponseListener, PurchasesResponseListener, ConsumeResponseListener, AcknowledgePurchaseResponseListener, BillingConfigResponseListener, AlternativeBillingOnlyReportingDetailsListener, AlternativeBillingOnlyAvailabilityListener,AlternativeBillingOnlyInformationDialogListener, ExternalOfferReportingDetailsListener, ExternalOfferAvailabilityListener,ExternalOfferInformationDialogListener, InAppMessageResponseListener { - private static final String TAG = CocosBilling.class.getSimpleName(); + private static final String TAG = GoogleBilling.class.getSimpleName(); private Map _productDetails = new HashMap<>(); private Map _purchase = new HashMap<>(); private int _productDetailsNextID = 0; @@ -81,7 +81,7 @@ public class CocosBilling implements BillingClientStateListener, */ private BillingClient _billingClient; - public CocosBilling() { + public GoogleBilling() { _billingClient = BillingClient.newBuilder(GlobalObject.getActivity()) .setListener(this) .enablePendingPurchases() @@ -327,7 +327,7 @@ public void onBillingSetupFinished(@NonNull BillingResult billingResult) { CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onBillingSetupFinished(billingResult); + GoogleBillingHelper.onBillingSetupFinished(billingResult); } }); } @@ -336,7 +336,7 @@ public void onBillingServiceDisconnected() { CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onBillingServiceDisconnected(); + GoogleBillingHelper.onBillingServiceDisconnected(); } }); } @@ -351,7 +351,7 @@ public void onProductDetailsResponse(@NonNull BillingResult billingResult, CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onProductDetailsResponse(billingResult, productDetailsList, startID); + GoogleBillingHelper.onProductDetailsResponse(billingResult, productDetailsList, startID); } }); } @@ -368,7 +368,7 @@ public void onPurchasesUpdated(@NonNull BillingResult billingResult, CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onPurchasesUpdated(billingResult, purchaseList, startID); + GoogleBillingHelper.onPurchasesUpdated(billingResult, purchaseList, startID); } }); } @@ -383,7 +383,7 @@ public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList, startID); + GoogleBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList, startID); } }); } @@ -394,7 +394,7 @@ public void onConsumeResponse(@NonNull BillingResult billingResult, CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onConsumeResponse(billingResult, purchaseToken); + GoogleBillingHelper.onConsumeResponse(billingResult, purchaseToken); } }); } @@ -404,7 +404,7 @@ public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onAcknowledgePurchaseResponse(billingResult); + GoogleBillingHelper.onAcknowledgePurchaseResponse(billingResult); } }); } @@ -414,7 +414,7 @@ public void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nulla CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onBillingConfigResponse(billingResult, billingConfig); + GoogleBillingHelper.onBillingConfigResponse(billingResult, billingConfig); } }); } @@ -426,7 +426,7 @@ public void onAlternativeBillingOnlyTokenResponse( CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onAlternativeBillingOnlyTokenResponse(billingResult, alternativeBillingOnlyReportingDetails); + GoogleBillingHelper.onAlternativeBillingOnlyTokenResponse(billingResult, alternativeBillingOnlyReportingDetails); } }); } @@ -436,7 +436,7 @@ public void onAlternativeBillingOnlyAvailabilityResponse(@NonNull BillingResult CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onAlternativeBillingOnlyAvailabilityResponse(billingResult); + GoogleBillingHelper.onAlternativeBillingOnlyAvailabilityResponse(billingResult); } }); } @@ -449,7 +449,7 @@ public void onExternalOfferReportingDetailsResponse( CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onExternalOfferReportingDetailsResponse(billingResult, externalOfferReportingDetails); + GoogleBillingHelper.onExternalOfferReportingDetailsResponse(billingResult, externalOfferReportingDetails); } }); } @@ -459,7 +459,7 @@ public void onExternalOfferAvailabilityResponse(@NonNull BillingResult billingRe CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onExternalOfferAvailabilityResponse(billingResult); + GoogleBillingHelper.onExternalOfferAvailabilityResponse(billingResult); } }); } @@ -469,7 +469,7 @@ public void onExternalOfferInformationDialogResponse(@NonNull BillingResult bill CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onExternalOfferInformationDialogResponse(billingResult); + GoogleBillingHelper.onExternalOfferInformationDialogResponse(billingResult); } }); } @@ -479,7 +479,7 @@ public void onAlternativeBillingOnlyInformationDialogResponse(@NonNull BillingRe CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onAlternativeBillingOnlyInformationDialogResponse(billingResult); + GoogleBillingHelper.onAlternativeBillingOnlyInformationDialogResponse(billingResult); } }); } @@ -489,7 +489,7 @@ public void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResul CocosHelper.runOnGameThread(new Runnable() { @Override public void run() { - CocosBillingHelper.onInAppMessageResponse(inAppMessageResult); + GoogleBillingHelper.onInAppMessageResponse(inAppMessageResult); } }); } diff --git a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java similarity index 97% rename from native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java rename to native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java index 5d862538f4d..3351911858f 100644 --- a/native/cocos/platform/android/java/vendor/com/cocos/billing/CocosBillingHelper.java +++ b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java @@ -22,7 +22,7 @@ of this software and associated documentation files (the "Software"), to deal THE SOFTWARE. ****************************************************************************/ -package com.cocos.billing; +package google.billing; import android.annotation.SuppressLint; @@ -38,9 +38,9 @@ of this software and associated documentation files (the "Software"), to deal import com.android.billingclient.api.Purchase; import java.util.List; import com.cocos.lib.GlobalObject; -import com.cocos.billing.CocosBilling; +import google.billing.GoogleBilling; -public class CocosBillingHelper { +public class GoogleBillingHelper { public static native void onBillingSetupFinished(@NonNull BillingResult billingResult); public static native void onBillingServiceDisconnected(); public static native void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList, int startID); @@ -64,11 +64,11 @@ public static native void onExternalOfferReportingDetailsResponse( public static native void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResult); @SuppressLint("StaticFieldLeak") - private static CocosBilling instance; + private static GoogleBilling instance; - public static CocosBilling getInstance() { + public static GoogleBilling getInstance() { if (instance == null) { - instance = new CocosBilling(); + instance = new GoogleBilling(); } return instance; } diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 8dedbe4acac..801cff10d39 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -21,7 +21,7 @@ android { manifest.srcFile "AndroidManifest.xml" } - if(project.hasProperty("PROP_ENABLE_BILLING") && project.PROP_ENABLE_BILLING.toBoolean()) { + if(project.hasProperty("PROP_ENABLE_GOOGLE_BILLING") && project.PROP_ENABLE_GOOGLE_BILLING.toBoolean()) { sourceSets.main.java.srcDirs += ["../java/vendor"] } buildDir = new File(rootProject.buildDir, project.name) @@ -40,7 +40,7 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') - if(project.hasProperty("PROP_ENABLE_BILLING") && project.PROP_ENABLE_BILLING.toBoolean()) { + if(project.hasProperty("PROP_ENABLE_GOOGLE_BILLING") && project.PROP_ENABLE_GOOGLE_BILLING.toBoolean()) { implementation "com.android.billingclient:billing:7.1.0" } } diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.cpp b/native/cocos/platform/android/modules/google_play/billing/billing.cpp deleted file mode 100644 index a5f00640d38..00000000000 --- a/native/cocos/platform/android/modules/google_play/billing/billing.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************** - Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -****************************************************************************/ - -#include "platform/android/modules/google_play/billing/billing.h" -#include "platform/android/modules/google_play/billing/JniBillingHelper.h" -#include "platform/java/jni/JniHelper.h" -#include "platform/java/jni/JniImp.h" - -namespace cc { - -ProductDetails::~ProductDetails() { - for (auto* ptr : subscriptionOfferDetails) { - delete ptr; - } - subscriptionOfferDetails.clear(); - JniBillingHelper::removeProductDetails(_id); -} - -Purchase::~Purchase() { - JniBillingHelper::removePurchase(_id); -} - -void Billing::startConnection() { - JniBillingHelper::startConnection(); -} - -void Billing::endConnection() { - JniBillingHelper::endConnection(); -} - -int Billing::getConnectionState() const { - return JniBillingHelper::getConnectionState(); -} - -bool Billing::isReady() const { - return JniBillingHelper::isReady(); -} - -void Billing::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { - JniBillingHelper::queryProductDetailsParams(productIds, type); -} - -void Billing::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { - JniBillingHelper::launchBillingFlow(productDetailsList, selectedOfferToken); -} - -void Billing::consumePurchases(const std::vector& purchases) { - JniBillingHelper::consumePurchases(purchases); -} - -void Billing::acknowledgePurchase(const std::vector& purchases) { - JniBillingHelper::acknowledgePurchase(purchases); -} - -void Billing::queryPurchasesAsync(const std::string& productType) { - JniBillingHelper::queryPurchasesAsync(productType); -} - -void Billing::getBillingConfigAsync() { - JniBillingHelper::getBillingConfigAsync(); -} - -BillingResult* Billing::isFeatureSupported(const std::string& feature) { - return JniBillingHelper::isFeatureSupported(feature); -} - -void Billing::createAlternativeBillingOnlyReportingDetailsAsync() { - JniBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync(); -} - -void Billing::isAlternativeBillingOnlyAvailableAsync() { - JniBillingHelper::isAlternativeBillingOnlyAvailableAsync(); -} - -void Billing::createExternalOfferReportingDetailsAsync() { - JniBillingHelper::createExternalOfferReportingDetailsAsync(); -} - -void Billing::isExternalOfferAvailableAsync() { - JniBillingHelper::isExternalOfferAvailableAsync(); -} - -BillingResult* Billing::showAlternativeBillingOnlyInformationDialog() { - return JniBillingHelper::showAlternativeBillingOnlyInformationDialog(); -} - -BillingResult* Billing::showExternalOfferInformationDialog() { - return JniBillingHelper::showExternalOfferInformationDialog(); -} - -BillingResult* Billing::showInAppMessages() { - return JniBillingHelper::showInAppMessages(); -} - -} // namespace cc diff --git a/native/cocos/platform/java/jni/JniCocosBilling.cpp b/native/cocos/platform/java/jni/JniCocosBilling.cpp deleted file mode 100644 index 5d76cf3ee9b..00000000000 --- a/native/cocos/platform/java/jni/JniCocosBilling.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** - Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. - - http://www.cocos.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -****************************************************************************/ - -#include "platform/java/jni/JniHelper.h" -#include -#include "cocos/platform/android/modules/google_play/billing/JniBillingHelper.h" - -extern "C" { - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onBillingSetupFinished(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { - cc::JniBillingHelper::onBillingServiceDisconnected(env, clazz); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, - jobject billingResultObj, - jobject productDetailsListObj, - jint startID) { - cc::JniBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj, startID); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, - jobject billingResultObj, - jobject purchaseListObj, - jint startID) { - cc::JniBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj, startID); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { - cc::JniBillingHelper::onConsumeResponse(env, clazz, billingResultObj, purchaseToken); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, - jobject purchaseListObj, jint startID) { - cc::JniBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj, startID); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onAcknowledgePurchaseResponse(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { - cc::JniBillingHelper::onBillingConfigResponse(env, clazz, billingResultObj, billingConfigObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { - cc::JniBillingHelper::onAlternativeBillingOnlyTokenResponse(env, clazz, billingResultObj, alternativeBillingOnlyReportingDetailsObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { - cc::JniBillingHelper::onExternalOfferReportingDetailsResponse(env, clazz, billingResultObj, externalOfferReportingDetailsObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onExternalOfferAvailabilityResponse(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { - cc::JniBillingHelper::onExternalOfferInformationDialogResponse(env, clazz, billingResultObj); -} - -// NOLINTNEXTLINE -JNIEXPORT void JNICALL Java_com_cocos_billing_CocosBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { - cc::JniBillingHelper::onInAppMessageResponse(env, clazz, inAppMessageResultObj); -} -} diff --git a/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp b/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp new file mode 100644 index 00000000000..a27ce9f0aa6 --- /dev/null +++ b/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/JniHelper.h" +#include +#include "vendor/google/billing/GoogleBillingHelper.h" + +extern "C" { + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onBillingSetupFinished(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { + cc::GoogleBillingHelper::onBillingServiceDisconnected(env, clazz); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj, + jint startID) { + cc::GoogleBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject purchaseListObj, + jint startID) { + cc::GoogleBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + cc::GoogleBillingHelper::onConsumeResponse(env, clazz, billingResultObj, purchaseToken); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, + jobject purchaseListObj, jint startID) { + cc::GoogleBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAcknowledgePurchaseResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + cc::GoogleBillingHelper::onBillingConfigResponse(env, clazz, billingResultObj, billingConfigObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyTokenResponse(env, clazz, billingResultObj, alternativeBillingOnlyReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + cc::GoogleBillingHelper::onExternalOfferReportingDetailsResponse(env, clazz, billingResultObj, externalOfferReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onExternalOfferAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onExternalOfferInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { + cc::GoogleBillingHelper::onInAppMessageResponse(env, clazz, inAppMessageResultObj); +} +} diff --git a/native/vendor/google/billing/GoogleBilling.cpp b/native/vendor/google/billing/GoogleBilling.cpp new file mode 100644 index 00000000000..04155f2ebb1 --- /dev/null +++ b/native/vendor/google/billing/GoogleBilling.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "vendor/google/billing/GoogleBilling.h" +#include "vendor/google/billing/GoogleBillingHelper.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +ProductDetails::~ProductDetails() { + for (auto* ptr : subscriptionOfferDetails) { + delete ptr; + } + subscriptionOfferDetails.clear(); + GoogleBillingHelper::removeProductDetails(_id); +} + +Purchase::~Purchase() { + GoogleBillingHelper::removePurchase(_id); +} + +void GoogleBilling::startConnection() { + GoogleBillingHelper::startConnection(); +} + +void GoogleBilling::endConnection() { + GoogleBillingHelper::endConnection(); +} + +int GoogleBilling::getConnectionState() const { + return GoogleBillingHelper::getConnectionState(); +} + +bool GoogleBilling::isReady() const { + return GoogleBillingHelper::isReady(); +} + +void GoogleBilling::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { + GoogleBillingHelper::queryProductDetailsParams(productIds, type); +} + +void GoogleBilling::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { + GoogleBillingHelper::launchBillingFlow(productDetailsList, selectedOfferToken); +} + +void GoogleBilling::consumePurchases(const std::vector& purchases) { + GoogleBillingHelper::consumePurchases(purchases); +} + +void GoogleBilling::acknowledgePurchase(const std::vector& purchases) { + GoogleBillingHelper::acknowledgePurchase(purchases); +} + +void GoogleBilling::queryPurchasesAsync(const std::string& productType) { + GoogleBillingHelper::queryPurchasesAsync(productType); +} + +void GoogleBilling::getBillingConfigAsync() { + GoogleBillingHelper::getBillingConfigAsync(); +} + +BillingResult* GoogleBilling::isFeatureSupported(const std::string& feature) { + return GoogleBillingHelper::isFeatureSupported(feature); +} + +void GoogleBilling::createAlternativeBillingOnlyReportingDetailsAsync() { + GoogleBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync(); +} + +void GoogleBilling::isAlternativeBillingOnlyAvailableAsync() { + GoogleBillingHelper::isAlternativeBillingOnlyAvailableAsync(); +} + +void GoogleBilling::createExternalOfferReportingDetailsAsync() { + GoogleBillingHelper::createExternalOfferReportingDetailsAsync(); +} + +void GoogleBilling::isExternalOfferAvailableAsync() { + GoogleBillingHelper::isExternalOfferAvailableAsync(); +} + +BillingResult* GoogleBilling::showAlternativeBillingOnlyInformationDialog() { + return GoogleBillingHelper::showAlternativeBillingOnlyInformationDialog(); +} + +BillingResult* GoogleBilling::showExternalOfferInformationDialog() { + return GoogleBillingHelper::showExternalOfferInformationDialog(); +} + +BillingResult* GoogleBilling::showInAppMessages() { + return GoogleBillingHelper::showInAppMessages(); +} + +} // namespace cc diff --git a/native/cocos/platform/android/modules/google_play/billing/billing.h b/native/vendor/google/billing/GoogleBilling.h similarity index 94% rename from native/cocos/platform/android/modules/google_play/billing/billing.h rename to native/vendor/google/billing/GoogleBilling.h index 9445c60e607..b394775f2f7 100644 --- a/native/cocos/platform/android/modules/google_play/billing/billing.h +++ b/native/vendor/google/billing/GoogleBilling.h @@ -159,10 +159,10 @@ class InAppMessageResult : public cc::RefCounted{ std::string purchaseToken; }; -class CC_DLL Billing { +class CC_DLL GoogleBilling { public: - static Billing &getInstance() { - static Billing instance; + static GoogleBilling &getInstance() { + static GoogleBilling instance; return instance; } @@ -186,7 +186,7 @@ class CC_DLL Billing { BillingResult* showExternalOfferInformationDialog(); BillingResult* showInAppMessages(); private: - Billing() = default; + GoogleBilling() = default; }; } // namespace cc diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp b/native/vendor/google/billing/GoogleBillingHelper.cpp similarity index 76% rename from native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp rename to native/vendor/google/billing/GoogleBillingHelper.cpp index f4335097242..fc34a726e39 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.cpp +++ b/native/vendor/google/billing/GoogleBillingHelper.cpp @@ -21,8 +21,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -#include "platform/android/modules/google_play/billing/JniBillingHelper.h" -#include "platform/android/modules/google_play/billing/billing.h" +#include "vendor/google/billing/GoogleBillingHelper.h" +#include "vendor/google/billing/GoogleBilling.h" #include "platform/java/jni/JniHelper.h" #include @@ -37,7 +37,7 @@ namespace { #ifndef JCLS_BILLING - #define JCLS_BILLING "com/cocos/billing/CocosBillingHelper" + #define JCLS_BILLING "google/billing/GoogleBillingHelper" #endif }; // namespace @@ -112,72 +112,72 @@ template void callJSfunc(const char*, BillingResult*&&, AlternativeBillingOnlyRe template void callJSfunc(const char*, BillingResult*&&, ExternalOfferReportingDetails*&&); template void callJSfunc(const char*, InAppMessageResult*&&); -void JniBillingHelper::removeProductDetails(int productDetailsID) { +void GoogleBillingHelper::removeProductDetails(int productDetailsID) { JniHelper::callStaticVoidMethod(JCLS_BILLING, "removeProductDetails", productDetailsID); } -void JniBillingHelper::removePurchase(int purchaseID) { +void GoogleBillingHelper::removePurchase(int purchaseID) { JniHelper::callStaticVoidMethod(JCLS_BILLING, "removePurchase", purchaseID); } -void JniBillingHelper::startConnection() { +void GoogleBillingHelper::startConnection() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "startConnection"); } -void JniBillingHelper::endConnection() { +void GoogleBillingHelper::endConnection() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "endConnection"); } -int JniBillingHelper::getConnectionState() { +int GoogleBillingHelper::getConnectionState() { return JniHelper::callStaticIntMethod(JCLS_BILLING, "getConnectionState"); } -bool JniBillingHelper::isReady() { +bool GoogleBillingHelper::isReady() { return JniHelper::callStaticBooleanMethod(JCLS_BILLING, "isReady"); } -void JniBillingHelper::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { +void GoogleBillingHelper::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryProductDetailsParams", productIds, type); } -void JniBillingHelper::queryPurchasesAsync(const std::string& productType) { +void GoogleBillingHelper::queryPurchasesAsync(const std::string& productType) { JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryPurchasesAsync", productType); } -void JniBillingHelper::getBillingConfigAsync() { +void GoogleBillingHelper::getBillingConfigAsync() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "getBillingConfigAsync"); } -void JniBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync() { +void GoogleBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "createAlternativeBillingOnlyReportingDetailsAsync"); } -void JniBillingHelper::isAlternativeBillingOnlyAvailableAsync() { +void GoogleBillingHelper::isAlternativeBillingOnlyAvailableAsync() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "isAlternativeBillingOnlyAvailableAsync"); } -void JniBillingHelper::createExternalOfferReportingDetailsAsync() { +void GoogleBillingHelper::createExternalOfferReportingDetailsAsync() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "createExternalOfferReportingDetailsAsync"); } -void JniBillingHelper::isExternalOfferAvailableAsync() { +void GoogleBillingHelper::isExternalOfferAvailableAsync() { JniHelper::callStaticVoidMethod(JCLS_BILLING, "isExternalOfferAvailableAsync"); } -BillingResult* JniBillingHelper::showAlternativeBillingOnlyInformationDialog() { +BillingResult* GoogleBillingHelper::showAlternativeBillingOnlyInformationDialog() { return callFunctionAndReturnBillingResult("showAlternativeBillingOnlyInformationDialog"); } -BillingResult* JniBillingHelper::showExternalOfferInformationDialog() { +BillingResult* GoogleBillingHelper::showExternalOfferInformationDialog() { return callFunctionAndReturnBillingResult("showExternalOfferInformationDialog"); } -BillingResult* JniBillingHelper::showInAppMessages() { +BillingResult* GoogleBillingHelper::showInAppMessages() { return callFunctionAndReturnBillingResult("showInAppMessages"); } -void JniBillingHelper::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { +void GoogleBillingHelper::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { if (productDetailsList.empty()) { return; } @@ -196,7 +196,7 @@ void JniBillingHelper::launchBillingFlow(const std::vector& pro t.env->CallStaticVoidMethod(t.classID, t.methodID, result, offerToken); } -void JniBillingHelper::consumePurchases(const std::vector& purchases) { +void GoogleBillingHelper::consumePurchases(const std::vector& purchases) { if (purchases.empty()) { return; } @@ -214,7 +214,7 @@ void JniBillingHelper::consumePurchases(const std::vector& purchases) t.env->CallStaticVoidMethod(t.classID, t.methodID, result); } -void JniBillingHelper::acknowledgePurchase(const std::vector& purchases) { +void GoogleBillingHelper::acknowledgePurchase(const std::vector& purchases) { if (purchases.empty()) { return; } @@ -232,7 +232,7 @@ void JniBillingHelper::acknowledgePurchase(const std::vector& purchas t.env->CallStaticVoidMethod(t.classID, t.methodID, result); } -BillingResult* JniBillingHelper::callFunctionAndReturnBillingResult(const std::string& functionName) { +BillingResult* GoogleBillingHelper::callFunctionAndReturnBillingResult(const std::string& functionName) { auto* env = JniHelper::getEnv(); cc::JniMethodInfo t; if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, functionName.c_str(), "()Lcom/android/billingclient/api/BillingResult;")) { @@ -242,7 +242,7 @@ BillingResult* JniBillingHelper::callFunctionAndReturnBillingResult(const std::s return nullptr; } -BillingResult* JniBillingHelper::isFeatureSupported(const std::string& feature) { +BillingResult* GoogleBillingHelper::isFeatureSupported(const std::string& feature) { auto* env = JniHelper::getEnv(); cc::JniMethodInfo t; if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "isFeatureSupported", "(Ljava/lang/String;)Lcom/android/billingclient/api/BillingResult;")) { @@ -253,104 +253,104 @@ BillingResult* JniBillingHelper::isFeatureSupported(const std::string& feature) return nullptr; } -void JniBillingHelper::onBillingSetupFinished(JNIEnv* env, jclass clazz, jobject billingResultObj) { +void GoogleBillingHelper::onBillingSetupFinished(JNIEnv* env, jclass clazz, jobject billingResultObj) { cc::callJSfunc("onBillingSetupFinished", toBillingResult(env, billingResultObj)); } -void JniBillingHelper::onBillingServiceDisconnected(JNIEnv* env, jclass clazz) { +void GoogleBillingHelper::onBillingServiceDisconnected(JNIEnv* env, jclass clazz) { cc::callJSfunc("onBillingServiceDisconnected"); } -void JniBillingHelper::onProductDetailsResponse(JNIEnv* env, jclass clazz, +void GoogleBillingHelper::onProductDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject productDetailsListObj, jint startID) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); - std::vector productDetailsList = cc::JniBillingHelper::toProductDetailList(env, productDetailsListObj, startID); + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + std::vector productDetailsList = cc::GoogleBillingHelper::toProductDetailList(env, productDetailsListObj, startID); cc::callJSfunc("onProductDetailsResponse", billingResult, productDetailsList); } -void JniBillingHelper::onPurchasesUpdated(JNIEnv* env, jclass clazz, +void GoogleBillingHelper::onPurchasesUpdated(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesListObj, jint startID) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); if (purchasesListObj != nullptr) { - std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj, startID); + std::vector purchasesList = cc::GoogleBillingHelper::toPurchaseList(env, purchasesListObj, startID); cc::callJSfunc("onPurchasesUpdated", billingResult, purchasesList); } else { cc::callJSfunc("onPurchasesUpdated", billingResult, std::vector()); } } -void JniBillingHelper::onConsumeResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onConsumeResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onConsumeResponse", billingResult, cc::StringUtils::getStringUTFCharsJNI(env, static_cast(purchaseToken))); } -void JniBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesListObj, jint startID) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); - std::vector purchasesList = cc::JniBillingHelper::toPurchaseList(env, purchasesListObj, startID); +void GoogleBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesListObj, jint startID) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + std::vector purchasesList = cc::GoogleBillingHelper::toPurchaseList(env, purchasesListObj, startID); cc::callJSfunc("onQueryPurchasesResponse", billingResult, purchasesList); } -void JniBillingHelper::onAcknowledgePurchaseResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onAcknowledgePurchaseResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onAcknowledgePurchaseResponse", billingResult); } -void JniBillingHelper::onBillingConfigResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onBillingConfigResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::BillingConfig* billingConfig = nullptr; if (billingConfigObj) { - billingConfig = cc::JniBillingHelper::toBillingConfig(env, billingConfigObj); + billingConfig = cc::GoogleBillingHelper::toBillingConfig(env, billingConfigObj); } cc::callJSfunc("onBillingConfigResponse", billingResult, billingConfig); } -void JniBillingHelper::onAlternativeBillingOnlyTokenResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onAlternativeBillingOnlyTokenResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::AlternativeBillingOnlyReportingDetails* toAlternativeBillingOnlyReporting = nullptr; if (alternativeBillingOnlyReportingDetailsObj) { - toAlternativeBillingOnlyReporting = cc::JniBillingHelper::toAlternativeBillingOnlyReportingDetails(env, alternativeBillingOnlyReportingDetailsObj); + toAlternativeBillingOnlyReporting = cc::GoogleBillingHelper::toAlternativeBillingOnlyReportingDetails(env, alternativeBillingOnlyReportingDetailsObj); } cc::callJSfunc("onAlternativeBillingOnlyTokenResponse", billingResult, toAlternativeBillingOnlyReporting); } -void JniBillingHelper::onExternalOfferReportingDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onExternalOfferReportingDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::ExternalOfferReportingDetails* externalOfferReportingDetails = nullptr; if (externalOfferReportingDetailsObj) { - externalOfferReportingDetails = cc::JniBillingHelper::toExternalOfferReportingDetails(env, externalOfferReportingDetailsObj); + externalOfferReportingDetails = cc::GoogleBillingHelper::toExternalOfferReportingDetails(env, externalOfferReportingDetailsObj); } cc::callJSfunc("onExternalOfferReportingDetailsResponse", billingResult, externalOfferReportingDetails); } -void JniBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onAlternativeBillingOnlyAvailabilityResponse", billingResult); } -void JniBillingHelper::onExternalOfferAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onExternalOfferAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onExternalOfferAvailabilityResponse", billingResult); } -void JniBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onAlternativeBillingOnlyInformationDialogResponse", billingResult); } -void JniBillingHelper::onExternalOfferInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { - auto* billingResult = cc::JniBillingHelper::toBillingResult(env, billingResultObj); +void GoogleBillingHelper::onExternalOfferInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); cc::callJSfunc("onExternalOfferInformationDialogResponse", billingResult); } -void JniBillingHelper::onInAppMessageResponse(JNIEnv* env, jclass clazz, jobject inAppMessageResultObj) { - auto* inAppMessageResult = cc::JniBillingHelper::toInAppMessageResult(env, inAppMessageResultObj); +void GoogleBillingHelper::onInAppMessageResponse(JNIEnv* env, jclass clazz, jobject inAppMessageResultObj) { + auto* inAppMessageResult = cc::GoogleBillingHelper::toInAppMessageResult(env, inAppMessageResultObj); cc::callJSfunc("onInAppMessageResponse", inAppMessageResult); } -cc::BillingResult* JniBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { +cc::BillingResult* GoogleBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* billingResult = ccnew cc::BillingResult; billingResult->debugMessage = callStringMethod(env, clazz, obj, "getDebugMessage"); @@ -359,7 +359,7 @@ cc::BillingResult* JniBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { return billingResult; } -cc::OneTimePurchaseOfferDetails* JniBillingHelper::toOneTimePurchaseOfferDetails(JNIEnv* env, jobject obj) { +cc::OneTimePurchaseOfferDetails* GoogleBillingHelper::toOneTimePurchaseOfferDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto oneTimePurchaseOfferDetails = new cc::OneTimePurchaseOfferDetails; oneTimePurchaseOfferDetails->formattedPrice = callStringMethod(env, clazz, obj, "getFormattedPrice"); @@ -368,7 +368,7 @@ cc::OneTimePurchaseOfferDetails* JniBillingHelper::toOneTimePurchaseOfferDetails return oneTimePurchaseOfferDetails; } -cc::InstallmentPlanDetails* JniBillingHelper::toInstallmentPlanDetails(JNIEnv* env, jobject obj) { +cc::InstallmentPlanDetails* GoogleBillingHelper::toInstallmentPlanDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* installmentPlanDetails = new cc::InstallmentPlanDetails; installmentPlanDetails->installmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "getInstallmentPlanCommitmentPaymentsCount"); @@ -376,7 +376,7 @@ cc::InstallmentPlanDetails* JniBillingHelper::toInstallmentPlanDetails(JNIEnv* e return installmentPlanDetails; } -cc::PricingPhase* JniBillingHelper::toPricingPhase(JNIEnv* env, jobject obj) { +cc::PricingPhase* GoogleBillingHelper::toPricingPhase(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* pricingPhase = new cc::PricingPhase; @@ -389,7 +389,7 @@ cc::PricingPhase* JniBillingHelper::toPricingPhase(JNIEnv* env, jobject obj) { return pricingPhase; } -cc::PricingPhases* JniBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { +cc::PricingPhases* GoogleBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* pricingPhases = new cc::PricingPhases; @@ -406,7 +406,7 @@ cc::PricingPhases* JniBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { return pricingPhases; } -cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEnv* env, jobject obj) { +cc::SubscriptionOfferDetails* GoogleBillingHelper::toSubscriptionOfferDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* details = ccnew cc::SubscriptionOfferDetails; details->basePlanId = callStringMethod(env, clazz, obj, @@ -434,21 +434,21 @@ cc::SubscriptionOfferDetails* JniBillingHelper::toSubscriptionOfferDetails(JNIEn return details; } -std::vector JniBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj, jint startID) { +std::vector GoogleBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj, jint startID) { jclass clazz = env->GetObjectClass(productListObj); jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productListObj, "size"); std::vector productDetailsList; for (int i = 0; i < size; ++i) { jobject productDetailObj = env->CallObjectMethod(productListObj, listGetMethod, i); - cc::ProductDetails* productDetails = cc::JniBillingHelper::toProductDetail(env, productDetailObj); + cc::ProductDetails* productDetails = cc::GoogleBillingHelper::toProductDetail(env, productDetailObj); productDetails->_id = startID++; productDetailsList.push_back(productDetails); } return std::move(productDetailsList); } -cc::ProductDetails* JniBillingHelper::toProductDetail(JNIEnv* env, jobject productObj) { +cc::ProductDetails* GoogleBillingHelper::toProductDetail(JNIEnv* env, jobject productObj) { jclass clazz = env->GetObjectClass(productObj); auto* productDetails = new cc::ProductDetails; @@ -483,7 +483,7 @@ cc::ProductDetails* JniBillingHelper::toProductDetail(JNIEnv* env, jobject produ return productDetails; } -cc::AccountIdentifiers* JniBillingHelper::toAccountIdentifiers(JNIEnv* env, jobject obj) { +cc::AccountIdentifiers* GoogleBillingHelper::toAccountIdentifiers(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* accountIdentifiers = new cc::AccountIdentifiers; accountIdentifiers->obfuscatedAccountId = callStringMethod(env, clazz, obj, "getObfuscatedAccountId"); @@ -491,7 +491,7 @@ cc::AccountIdentifiers* JniBillingHelper::toAccountIdentifiers(JNIEnv* env, jobj return accountIdentifiers; } -cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env, jobject obj) { +cc::PendingPurchaseUpdate* GoogleBillingHelper::toPendingPurchaseUpdate(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* pendingPurchaseUpdate = new cc::PendingPurchaseUpdate; jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/ArrayList;"); @@ -507,21 +507,21 @@ cc::PendingPurchaseUpdate* JniBillingHelper::toPendingPurchaseUpdate(JNIEnv* env return pendingPurchaseUpdate; } -std::vector JniBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj, jint startID) { +std::vector GoogleBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj, jint startID) { jclass clazz = env->GetObjectClass(productsListObj); jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); int size = callIntMethod(env, clazz, productsListObj, "size"); std::vector purchases; for (int i = 0; i < size; ++i) { jobject purchaseObj = env->CallObjectMethod(productsListObj, listGetMethod, i); - cc::Purchase* purchase = cc::JniBillingHelper::toPurchase(env, purchaseObj); + cc::Purchase* purchase = cc::GoogleBillingHelper::toPurchase(env, purchaseObj); purchase->_id = startID++; purchases.push_back(purchase); } return std::move(purchases); } -cc::Purchase* JniBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { +cc::Purchase* GoogleBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { jclass clazz = env->GetObjectClass(purchaseObj); auto* purchase = new cc::Purchase; @@ -564,28 +564,28 @@ cc::Purchase* JniBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { return purchase; } -cc::BillingConfig* JniBillingHelper::toBillingConfig(JNIEnv* env, jobject obj) { +cc::BillingConfig* GoogleBillingHelper::toBillingConfig(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* billingConfig = new cc::BillingConfig; billingConfig->countryCode = callStringMethod(env, clazz, obj, "getCountryCode"); return billingConfig; } -cc::AlternativeBillingOnlyReportingDetails* JniBillingHelper::toAlternativeBillingOnlyReportingDetails(JNIEnv* env, jobject obj) { +cc::AlternativeBillingOnlyReportingDetails* GoogleBillingHelper::toAlternativeBillingOnlyReportingDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* alternativeBillingOnlyReportingDetails = new cc::AlternativeBillingOnlyReportingDetails; alternativeBillingOnlyReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); return alternativeBillingOnlyReportingDetails; } -cc::ExternalOfferReportingDetails* JniBillingHelper::toExternalOfferReportingDetails(JNIEnv* env, jobject obj) { +cc::ExternalOfferReportingDetails* GoogleBillingHelper::toExternalOfferReportingDetails(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* externalOfferReportingDetails = new cc::ExternalOfferReportingDetails; externalOfferReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); return externalOfferReportingDetails; } -cc::InAppMessageResult* JniBillingHelper::toInAppMessageResult(JNIEnv* env, jobject obj) { +cc::InAppMessageResult* GoogleBillingHelper::toInAppMessageResult(JNIEnv* env, jobject obj) { jclass clazz = env->GetObjectClass(obj); auto* inAppMessageResult = new cc::InAppMessageResult; inAppMessageResult->responseCode = callIntMethod(env, clazz, obj, "getResponseCode"); diff --git a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h b/native/vendor/google/billing/GoogleBillingHelper.h similarity index 99% rename from native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h rename to native/vendor/google/billing/GoogleBillingHelper.h index 33df0534b29..bd73a35a9b5 100644 --- a/native/cocos/platform/android/modules/google_play/billing/JniBillingHelper.h +++ b/native/vendor/google/billing/GoogleBillingHelper.h @@ -46,7 +46,7 @@ class AlternativeBillingOnlyReportingDetails; class ExternalOfferReportingDetails; class InAppMessageResult; -class CC_DLL JniBillingHelper { +class CC_DLL GoogleBillingHelper { public: static void removeProductDetails(int productDetailsID); static void removePurchase(int purchaseID); diff --git a/scripts/native-pack-tool/source/platforms/google-play.ts b/scripts/native-pack-tool/source/platforms/google-play.ts index 1ce044567aa..dc02b01b25e 100644 --- a/scripts/native-pack-tool/source/platforms/google-play.ts +++ b/scripts/native-pack-tool/source/platforms/google-play.ts @@ -13,6 +13,21 @@ export interface IOrientation { portrait: boolean; upsideDown: boolean; } +/** + * 默认 CustomIconList,存放在 static 下的 icon 路径 + */ +export interface ICustomIconDpi { + fileName: string; + dirName: string; + dpi: number; + path: string; +} + +export interface ICustomIconInfo { + type: string, + display: string, + list: ICustomIconDpi[] +} export interface IAndroidParams { packageName: string; @@ -34,9 +49,13 @@ export interface IAndroidParams { orientation: IOrientation; appBundle: boolean; resizeableActivity: boolean; + googleBilling: boolean; + customIconInfo: ICustomIconInfo, } const DefaultAPILevel = 27; + + export class GooglePlayPackTool extends NativePackTool { params!: CocosParams; @@ -59,6 +78,26 @@ export class GooglePlayPackTool extends NativePackTool { } else { this.validateNativeDir(); } + + // 替换icon + await this.copyCustomIcons(this.params.platformParams.customIconInfo); + } + + /** + * 拷贝自定义 ICON + * @param type - 类型自定义还是默认 + * @param buildDir - 构建路径 + * @param outputName - 用于获取 CustomIcon + */ + protected async copyCustomIcons(customIconInfo: ICustomIconInfo) { + const tasks = customIconInfo.list.map(async (item) => { + const distRoot = ps.join(this.paths.platformTemplateDirInPrj, 'res', item.dirName); + + await fs.ensureDir(distRoot); + const filePath = ps.join(distRoot, item.fileName); + await fs.copy(item.path, filePath); + }); + await Promise.all(tasks); } protected validatePlatformDirectory(missing: string[]): void { @@ -364,6 +403,9 @@ export class GooglePlayPackTool extends NativePackTool { content = content.replace(/APPLICATION_ID=.*/, `APPLICATION_ID=${options.packageName}`); content = content.replace(/NATIVE_DIR=.*/, `NATIVE_DIR=${cchelper.fixPath(this.paths.platformTemplateDirInPrj)}`); + content = content.replace(/PROP_ENABLE_GOOGLE_BILLING=.*/, `PROP_ENABLE_GOOGLE_BILLING=${options.googleBilling ? "true" : "false"}`); + + if (process.platform === 'win32') { options.ndkPath = options.ndkPath.replace(/\\/g, '\\\\'); diff --git a/templates/google-play/build/gradle.properties b/templates/google-play/build/gradle.properties index ba524fd48e9..c52b0a50848 100644 --- a/templates/google-play/build/gradle.properties +++ b/templates/google-play/build/gradle.properties @@ -53,7 +53,7 @@ PROP_IS_DEBUG=true COCOS_ENGINE_PATH= # Billing -PROP_ENABLE_BILLING=true +PROP_ENABLE_GOOGLE_BILLING=true # Res path RES_PATH= diff --git a/vendor/google/play/billing/billing.ts b/vendor/google/billing/billing.ts similarity index 91% rename from vendor/google/play/billing/billing.ts rename to vendor/google/billing/billing.ts index d2fc57f3397..c9545f20ca2 100644 --- a/vendor/google/play/billing/billing.ts +++ b/vendor/google/billing/billing.ts @@ -22,7 +22,7 @@ THE SOFTWARE. *****************************************************************************/ import { JSB } from 'internal:constants'; -import { EventTarget } from '../../../../cocos/core/event'; +import { EventTarget } from '../../../cocos/core/event'; interface BillingEventMap { [google.BillingEventType.BILLING_SETUP_FINISHED]: (result: google.BillingResult) => void, @@ -48,7 +48,7 @@ interface BillingEventMap { class Billing { private _eventTarget: EventTarget = new EventTarget(); constructor () { - if (!JSB || !jsb.billing) { + if (!JSB || !jsb.googleBilling) { return; } jsb.onBillingSetupFinished = (result: google.BillingResult): void => { @@ -132,7 +132,7 @@ class Billing { * @zh 异步启动 BillingClient 设置过程。 */ public startConnection (): void { - jsb.billing?.startConnection(); + jsb.googleBilling?.startConnection(); } /** @@ -140,7 +140,7 @@ class Billing { * @zh 关闭连接并释放所有持有的资源,例如服务连接。 */ public endConnection (): void { - jsb.billing?.endConnection(); + jsb.googleBilling?.endConnection(); } /** @@ -148,9 +148,9 @@ class Billing { * @zh 获取当前billing客户端连接状态。 */ public getConnectionState (): number { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.getConnectionState(); + return jsb.googleBilling.getConnectionState(); } return google.ConnectionState.DISCONNECTED; } @@ -162,9 +162,9 @@ class Billing { 如果客户端当前已连接到服务,则返回 true,否则返回 false。 */ public isReady (): boolean { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isReady(); + return jsb.googleBilling.isReady(); } return false; } @@ -178,11 +178,11 @@ class Billing { */ public queryProductDetailsParams (productId: string[] | string, productType: google.ProductType): void { if (productId instanceof Array) { - jsb.billing?.queryProductDetailsParams(productId, productType); + jsb.googleBilling?.queryProductDetailsParams(productId, productType); } else { const productIdList: string[] = []; productIdList.push(productId); - jsb.billing?.queryProductDetailsParams(productIdList, productType); + jsb.googleBilling?.queryProductDetailsParams(productIdList, productType); } } @@ -194,11 +194,11 @@ class Billing { */ public launchBillingFlow (productDetails: google.ProductDetails[] | google.ProductDetails, selectedOfferToken: string | null): void { if (productDetails instanceof Array) { - jsb.billing?.launchBillingFlow(productDetails, selectedOfferToken); + jsb.googleBilling?.launchBillingFlow(productDetails, selectedOfferToken); } else { const productDetailsList: google.ProductDetails[] = []; productDetailsList.push(productDetails); - jsb.billing?.launchBillingFlow(productDetailsList, selectedOfferToken); + jsb.googleBilling?.launchBillingFlow(productDetailsList, selectedOfferToken); } } @@ -209,11 +209,11 @@ class Billing { */ public consumePurchases (purchase: google.Purchase[] | google.Purchase): void { if (purchase instanceof Array) { - jsb.billing?.consumePurchases(purchase); + jsb.googleBilling?.consumePurchases(purchase); } else { const purchaseList: google.Purchase[] = []; purchaseList.push(purchase); - jsb.billing?.consumePurchases(purchaseList); + jsb.googleBilling?.consumePurchases(purchaseList); } } @@ -224,11 +224,11 @@ class Billing { */ public acknowledgePurchase (purchase: google.Purchase[] | google.Purchase): void { if (purchase instanceof Array) { - jsb.billing?.acknowledgePurchase(purchase); + jsb.googleBilling?.acknowledgePurchase(purchase); } else { const purchaseList: google.Purchase[] = []; purchaseList.push(purchase); - jsb.billing?.acknowledgePurchase(purchaseList); + jsb.googleBilling?.acknowledgePurchase(purchaseList); } } @@ -238,7 +238,7 @@ class Billing { * @param productType @zh 产品类型 @en Product type. */ public queryPurchasesAsync (productType: google.ProductType): void { - jsb.billing?.queryPurchasesAsync(productType); + jsb.googleBilling?.queryPurchasesAsync(productType); } /** @@ -246,7 +246,7 @@ class Billing { * @zh 获取计费配置,其中存储用于执行计费操作的配置。 */ public getBillingConfigAsync (): void { - jsb.billing?.getBillingConfigAsync(); + jsb.googleBilling?.getBillingConfigAsync(); } /** @@ -255,7 +255,7 @@ class Billing { * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 */ public createAlternativeBillingOnlyReportingDetailsAsync (): void { - jsb.billing?.createAlternativeBillingOnlyReportingDetailsAsync(); + jsb.googleBilling?.createAlternativeBillingOnlyReportingDetailsAsync(); } /** @@ -263,7 +263,7 @@ class Billing { * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 */ public isAlternativeBillingOnlyAvailableAsync (): void { - jsb.billing?.isAlternativeBillingOnlyAvailableAsync(); + jsb.googleBilling?.isAlternativeBillingOnlyAvailableAsync(); } /** @@ -271,7 +271,7 @@ class Billing { * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 */ public createExternalOfferReportingDetailsAsync (): void { - jsb.billing?.createExternalOfferReportingDetailsAsync(); + jsb.googleBilling?.createExternalOfferReportingDetailsAsync(); } /** @@ -279,7 +279,7 @@ class Billing { * @zh 检查提供外部报价的可用性。 */ public isExternalOfferAvailableAsync (): void { - jsb.billing?.isExternalOfferAvailableAsync(); + jsb.googleBilling?.isExternalOfferAvailableAsync(); } /** @@ -288,9 +288,9 @@ class Billing { * @param feature @zh 功能特性 @en feature. */ public isFeatureSupported (feature: string): google.BillingResult | null { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.isFeatureSupported(feature); + return jsb.googleBilling.isFeatureSupported(feature); } return null; } @@ -300,9 +300,9 @@ class Billing { * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 */ public showAlternativeBillingOnlyInformationDialog (): google.BillingResult | null { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showAlternativeBillingOnlyInformationDialog(); + return jsb.googleBilling.showAlternativeBillingOnlyInformationDialog(); } return null; } @@ -312,9 +312,9 @@ class Billing { * @zh 在调用应用程序顶部显示外部优惠信息对话框。 */ public showExternalOfferInformationDialog (): google.BillingResult | null { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showExternalOfferInformationDialog(); + return jsb.googleBilling.showExternalOfferInformationDialog(); } return null; } @@ -324,9 +324,9 @@ class Billing { * @zh 在调用应用程序上叠加与计费相关的消息。 */ public showInAppMessages (): google.BillingResult | null { - if (jsb.billing) { + if (jsb.googleBilling) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return jsb.billing.showInAppMessages(); + return jsb.googleBilling.showInAppMessages(); } return null; } diff --git a/vendor/google/play/index.ts b/vendor/google/index.ts similarity index 100% rename from vendor/google/play/index.ts rename to vendor/google/index.ts From 45176658f105c98e3a636e74faee562c147d5578 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Thu, 28 Nov 2024 19:39:07 +0800 Subject: [PATCH 10/13] Fix name typo --- native/CMakeLists.txt | 2 +- .../java/vendor/google/billing/GoogleBillingHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index b001e9cdba7..2cbbd620e44 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -3146,7 +3146,7 @@ function(cc_apply_definations target) $<$:SCRIPT_ENGINE_TYPE=5> $<$:SCRIPT_ENGINE_TYPE=6> $<$,$>:CC_DEBUG=1> - $,CC_USE_APDF=1,CC_USE_USE_APDF=0> + $,CC_USE_APDF=1,CC_USE_APDF=0> $,CC_USE_GOOGLE_BILLING=1,CC_USE_GOOGLE_BILLING=0> ) endfunction() diff --git a/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java index 3351911858f..2cef42b49e2 100644 --- a/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java +++ b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java @@ -74,11 +74,11 @@ public static GoogleBilling getInstance() { } public static void removeProductDetails(int productDetailsID) { - instance.removeProductDetails(productDetailsID); + getInstance().removeProductDetails(productDetailsID); } public static void removePurchase(int purchaseID) { - instance.removeProductDetails(purchaseID); + getInstance().removeProductDetails(purchaseID); } public static void startConnection() { From ac43b00e51fbcaea7183d55171983b0eed7487a3 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Fri, 29 Nov 2024 09:24:01 +0800 Subject: [PATCH 11/13] 1.Modify vendor to vendor-google 2. Adjusting the interface to support only arrays --- cc.config.json | 2 +- editor/engine-features/render-config.json | 2 +- exports/{vendor.ts => vendor-google.ts} | 0 vendor/google/billing/billing.ts | 40 +++++------------------ 4 files changed, 10 insertions(+), 34 deletions(-) rename exports/{vendor.ts => vendor-google.ts} (100%) diff --git a/cc.config.json b/cc.config.json index e8e06cd5091..378729639b5 100644 --- a/cc.config.json +++ b/cc.config.json @@ -160,7 +160,7 @@ "dependentModules": ["2d"] }, "vendor": { - "modules": ["vendor"] + "modules": ["vendor-google"] }, "spine": { "modules": ["spine"], diff --git a/editor/engine-features/render-config.json b/editor/engine-features/render-config.json index d121ba7a9d5..11fb2949889 100644 --- a/editor/engine-features/render-config.json +++ b/editor/engine-features/render-config.json @@ -318,7 +318,7 @@ "description": "i18n:ENGINE.features.vendor.description", "enginePlugin": false, "isNativeModule": true, - "cmakeConfig": "USE_GOOGLE_BILLING", + "cmakeConfig": "USE_VENDOR", "hidden": true }, "spine": { diff --git a/exports/vendor.ts b/exports/vendor-google.ts similarity index 100% rename from exports/vendor.ts rename to exports/vendor-google.ts diff --git a/vendor/google/billing/billing.ts b/vendor/google/billing/billing.ts index c9545f20ca2..c31f64ad209 100644 --- a/vendor/google/billing/billing.ts +++ b/vendor/google/billing/billing.ts @@ -176,14 +176,8 @@ class Billing { * @param productType @zh 产品类型。 @en product type. * */ - public queryProductDetailsParams (productId: string[] | string, productType: google.ProductType): void { - if (productId instanceof Array) { - jsb.googleBilling?.queryProductDetailsParams(productId, productType); - } else { - const productIdList: string[] = []; - productIdList.push(productId); - jsb.googleBilling?.queryProductDetailsParams(productIdList, productType); - } + public queryProductDetailsParams (productId: string[], productType: google.ProductType): void { + jsb.googleBilling?.queryProductDetailsParams(productId, productType); } /** @@ -192,14 +186,8 @@ class Billing { * @param productDetails @zh 产品详情。 @en product details. * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. */ - public launchBillingFlow (productDetails: google.ProductDetails[] | google.ProductDetails, selectedOfferToken: string | null): void { - if (productDetails instanceof Array) { - jsb.googleBilling?.launchBillingFlow(productDetails, selectedOfferToken); - } else { - const productDetailsList: google.ProductDetails[] = []; - productDetailsList.push(productDetails); - jsb.googleBilling?.launchBillingFlow(productDetailsList, selectedOfferToken); - } + public launchBillingFlow (productDetails: google.ProductDetails[], selectedOfferToken: string | null): void { + jsb.googleBilling?.launchBillingFlow(productDetails, selectedOfferToken); } /** @@ -207,14 +195,8 @@ class Billing { * @zh 消费指定的应用内产品。 * @param purchase @zh 已经购买的产品。 @en Purchased Products. */ - public consumePurchases (purchase: google.Purchase[] | google.Purchase): void { - if (purchase instanceof Array) { - jsb.googleBilling?.consumePurchases(purchase); - } else { - const purchaseList: google.Purchase[] = []; - purchaseList.push(purchase); - jsb.googleBilling?.consumePurchases(purchaseList); - } + public consumePurchases (purchase: google.Purchase[]): void { + jsb.googleBilling?.consumePurchases(purchase); } /** @@ -222,14 +204,8 @@ class Billing { * @zh 确认应用内购买。 * @param purchase @zh 已经购买的产品。 @en Purchased Products. */ - public acknowledgePurchase (purchase: google.Purchase[] | google.Purchase): void { - if (purchase instanceof Array) { - jsb.googleBilling?.acknowledgePurchase(purchase); - } else { - const purchaseList: google.Purchase[] = []; - purchaseList.push(purchase); - jsb.googleBilling?.acknowledgePurchase(purchaseList); - } + public acknowledgePurchase (purchase: google.Purchase[]): void { + jsb.googleBilling?.acknowledgePurchase(purchase); } /** From a4d5e58c7c8bb050f567cd4e22df9eef752223ae Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Fri, 29 Nov 2024 10:02:25 +0800 Subject: [PATCH 12/13] Fix document export --- typedoc-index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typedoc-index.ts b/typedoc-index.ts index 2e0cd205648..c25b3950dbe 100644 --- a/typedoc-index.ts +++ b/typedoc-index.ts @@ -29,4 +29,4 @@ export * from './exports/video'; export * from './exports/webview'; export * from './exports/sorting'; export * from './exports/xr'; -export * from './exports/vendor'; +export * from './exports/vendor-google'; From 1a426da509bec7e01e325e9c4d8e9eb9f2062ea1 Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Fri, 29 Nov 2024 10:17:59 +0800 Subject: [PATCH 13/13] Fix ci compilation errors in openharmony --- .github/workflows/native-compile-platforms.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/native-compile-platforms.yml b/.github/workflows/native-compile-platforms.yml index eaf7a4c84bc..d9ac157c3d2 100644 --- a/.github/workflows/native-compile-platforms.yml +++ b/.github/workflows/native-compile-platforms.yml @@ -327,7 +327,7 @@ jobs: NATIVE_ROOT=$GITHUB_WORKSPACE/native echo "Compiling Openharmony ... " - cd $GITHUB_WORKSPACE/templates/openharmony + cd $GITHUB_WORKSPACE/templates/harmonyos-next echo "message(STATUS \"hook before \${CC_TARGET_NAME}\")" >> Pre-Sample.cmake echo "message(STATUS \"hook after \${CC_TARGET_NAME}\")" >> Post-Sample.cmake @@ -350,7 +350,7 @@ jobs: mkdir -p build-oh/assets - ASSET_DIR=$GITHUB_WORKSPACE/templates/openharmony/build-oh + ASSET_DIR=$GITHUB_WORKSPACE/templates/harmonyos-next/build-oh echo "=============== HACK ./entry/build-profile.json5 ===============" sed -i "s@[^-]DRES_DIR[^=]@$ASSET_DIR@g" ./entry/build-profile.json5