From de804f7fc7ebed298786afd22532334e0ecf7002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AB=E6=AC=A3=E6=B5=B7?= Date: Sat, 12 Apr 2025 19:07:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E6=B8=B8=E6=88=8F=E5=B9=BF=E5=91=8A,?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=92=8C=E4=B8=80=E4=BA=9B=E5=B8=B8=E7=94=A8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/lib.ali.api.d.ts | 9 + libs/lib.bytedance.api.d.ts | 375 ++++++++++++++++ libs/lib.wx.api.d.ts | 511 +++++++++++++++++++++- src/hotupdate/HotUpdateManager.ts | 30 +- src/kunpocc.ts | 7 + src/minigame/MiniHelper.ts | 68 +++ src/minigame/alipay/AlipayAds.ts | 75 ++++ src/minigame/alipay/AlipayCommon.ts | 119 +++++ src/minigame/alipay/AlipayPay.ts | 72 +++ src/minigame/bytedance/BytedanceAds.ts | 81 ++++ src/minigame/bytedance/BytedanceCommon.ts | 125 ++++++ src/minigame/bytedance/BytedancePay.ts | 125 ++++++ src/minigame/header.ts | 24 + src/minigame/interface/IMiniAds.ts | 20 + src/minigame/interface/IMiniCommon.ts | 53 +++ src/minigame/interface/IMiniPay.ts | 69 +++ src/minigame/wechat/WechatAds.ts | 78 ++++ src/minigame/wechat/WechatCommon.ts | 208 +++++++++ src/minigame/wechat/WechatPay.ts | 67 +++ src/net/socket/Socket.ts | 13 +- src/tool/Utils.ts | 39 ++ tsconfig.json | 3 +- 22 files changed, 2134 insertions(+), 37 deletions(-) create mode 100644 libs/lib.bytedance.api.d.ts create mode 100644 src/minigame/MiniHelper.ts create mode 100644 src/minigame/alipay/AlipayAds.ts create mode 100644 src/minigame/alipay/AlipayCommon.ts create mode 100644 src/minigame/alipay/AlipayPay.ts create mode 100644 src/minigame/bytedance/BytedanceAds.ts create mode 100644 src/minigame/bytedance/BytedanceCommon.ts create mode 100644 src/minigame/bytedance/BytedancePay.ts create mode 100644 src/minigame/header.ts create mode 100644 src/minigame/interface/IMiniAds.ts create mode 100644 src/minigame/interface/IMiniCommon.ts create mode 100644 src/minigame/interface/IMiniPay.ts create mode 100644 src/minigame/wechat/WechatAds.ts create mode 100644 src/minigame/wechat/WechatCommon.ts create mode 100644 src/minigame/wechat/WechatPay.ts create mode 100644 src/tool/Utils.ts diff --git a/libs/lib.ali.api.d.ts b/libs/lib.ali.api.d.ts index 7d1d540..0399dc6 100644 --- a/libs/lib.ali.api.d.ts +++ b/libs/lib.ali.api.d.ts @@ -3516,6 +3516,15 @@ interface My { * */ getLaunchOptionsSync(): AliyMiniprogram.AppLaunchOptions; + getEnterOptionsSync(): AliyMiniprogram.AppLaunchOptions; + requestGamePayment(data: { + customId: string, + buyQuantity: number, + extraInfo?: Record, + success?: (res: { resultCode: number }) => void, + fail?: (res: CallBack.Fail) => void, + complete?: () => void + }): void; /** 监听小游戏回到前台的事件 */ onShow(listener: (res: AliyMiniprogram.AppLaunchOptions) => void); diff --git a/libs/lib.bytedance.api.d.ts b/libs/lib.bytedance.api.d.ts new file mode 100644 index 0000000..e7edca5 --- /dev/null +++ b/libs/lib.bytedance.api.d.ts @@ -0,0 +1,375 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 字节跳动 API 类型定义 + */ + +declare namespace BytedanceMiniprogram { + type IAnyObject = Record + + type GeneralSuccessCallback = (res: GeneralSuccessResult) => void; + type GeneralFailCallback = (res: GeneralFailResult) => void; + type GeneralCompleteCallback = (res: any) => void; + + interface GeneralSuccessResult { + errMsg: string; + } + + interface GeneralFailCodeResult { + errCode: number; + errMsg: string; + } + + interface GeneralFailResult { + errMsg: string; + errNo?: number; + } + + /** 获取版本信息和环境变量 */ + interface EnvInfo { + /** 小程序信息 */ + microapp: { + /** 小程序版本号 */ + mpVersion: string; + /** 小程序环境 */ + envType: string; + /** 小程序appId */ + appId: string; + }; + /** 插件信息 */ + plugin: Record; + /** 通用参数 */ + common: { + /** 用户数据存储的路径 */ + USER_DATA_PATH: string; + /** 校验白名单属性中的appInfoLaunchFrom后返回额外信息 */ + location: string | undefined; + launchFrom: string | undefined; + schema: string | undefined; + }; + } + + interface SystemInfo { + /** 操作系统版本 */ + system: string; + /** 操作系统类型 */ + platform: string; + /** 手机品牌 */ + brand: string; + /** 手机型号 */ + model: string; + /** 宿主 App 版本号 */ + version: string; + /** + * 宿主 APP 名称 + * + * - Toutiao 今日头条 + * - Douyin 抖音(国内版) + * - news_article_lite 今日头条(极速版) + * - live_stream 火山小视频 + * - XiGua 西瓜 + * - PPX 皮皮虾 + */ + appName: string; + /** 客户端基础库版本 */ + SDKVersion: string; + /** 屏幕宽度 */ + screenWidth: number; + /** 屏幕高度 */ + screenHeight: number; + /** 可使用窗口宽度 */ + windowWidth: number; + /** 可使用窗口高度 */ + windowHeight: number; + /** 设备像素比 */ + pixelRatio: number; + /** 状态栏的高度,单位 px */ + statusBarHeight: number; + /** 在竖屏正方向下的安全区域 */ + safeArea: { + /** 安全区域左上角横坐标 */ + left: number; + /** 安全区域右下角横坐标 */ + right: number; + /** 安全区域左上角纵坐标 */ + top: number; + /** 安全区域右下角纵坐标 */ + bottom: number; + /** 安全区域的宽度,单位逻辑像素 */ + width: number; + /** 安全区域的高度,单位逻辑像素 */ + height: number; + }; + } + + + interface LaunchParams { + /** + * 小程序启动页面路径 + * @version 1.12.0 + */ + path: string; + /** + * 小程序启动场景值 + * @version 1.12.0 + */ + scene: string; + /** + * 小程序启动参数 + * @version 1.12.0 + */ + query: object; + /** + * 来源信息。从另一个小程序进入小程序时返回。否则返回 {}。 + * @version 1.15.0 + */ + referrerInfo: { + /** 来源小程序的 appId */ + appId: string; + /** 来源小程序传过来的数据,场景值为 011009 或 011010 时支持。 */ + extraData: object; + }; + /** + * 唤起小程序页面的来源方式,10 表示用户点击小程序入口 schema,0 表示其它方式,比如前后台切换 + * @version 1.90.0 + */ + showFrom: number; + } + + + interface SocketTask { + /** + * 表示 Socket 连接状态 code + * 若由于参数错误导致未创建连接, 则为 undefined + */ + readonly readyState?: 0 | 1 | 2 | 3; + /** 表示 Socket 正在连接的常量 */ + readonly CONNECTING: 0; + /** 表示 Socket 连接已经打开的常量 */ + readonly OPEN: 1; + /** 表示 Socket 连接关闭中的常量 */ + readonly CLOSING: 2; + /** 表示 Socket 连接已关闭的常量 */ + readonly CLOSED: 3; + + /** + * ### WebSocket 发送给服务端数据的方法 + */ + send: (res: { + data: string | ArrayBuffer, + success?: GeneralSuccessCallback, + fail?: GeneralFailCallback, + complete?: GeneralCompleteCallback + }) => void; + + /** ### 关闭 WebSocket 连接的方法。 */ + close: (res: { + code?: number, + reason?: string, + success?: GeneralSuccessCallback, + fail?: GeneralFailCallback, + complete?: GeneralCompleteCallback + }) => void; + + /** + * ### 监听 WebSocket 连接服务器成功的事件 + * 表示 WebSocket 的状态变成 open,可以发送数据给服务器。 + */ + onOpen: ( + callback: (res: { + /** 连接服务器返回的 Response Header */ + header: Record; + /** 使用的网络传输层协议 */ + protocolType: string; + /** websocket 类型 */ + socketType: "ttnet" | "tradition"; + }) => void + ) => void; + + /** 监听 WebSocket 与服务器的连接断开的事件 */ + onClose: ( + callback: (res: { + /** 使用的网络传输层协议 */ + protocolType: string; + /** websocket 类型 */ + socketType: string; + /** 错误信息 */ + errMsg: string; + /** 关闭原因 */ + reason: string; + /** 关闭 code */ + code: string; + }) => void + ) => void; + + /** ### 监听 WebSocket 接收到服务器发送信息的事件。 */ + onMessage: ( + callback: (res: { + /** 接收到的服务器消息 */ + data: string | ArrayBuffer; + /** websocket 使用的协议 */ + protocolType: string; + /** websocket 类型 */ + socketType: "ttnet" | "tradition"; + }) => void + ) => void; + + /** ### 监听 WebSocket 发生错误的事件 */ + onError: ( + callback: (res: { + /** 错误信息 */ + errMsg: string; + }) => void + ) => void; + } + + + interface RewardedVideoAd { + /** 广告创建后默认是隐藏的,可以通过该方法显示广告 */ + show: () => Promise; + /** 当广告素材加载出现错误时,可以通过 load 方法手动加载 */ + load: () => Promise; + /** 绑定广告 load 事件的监听器 */ + onLoad: (fn: Callback) => void; + /** 解除绑定 load 事件的监听器 */ + offLoad: (fn: Callback) => void; + /** 绑定 error 事件的监听器 */ + onError: (fn: (data: GeneralFailCodeResult) => void) => void; + /** 解除绑定 error 事件的监听器 */ + onClose: (fn: (data: { isEnded: boolean, count?: number }) => void) => void; + /** 绑定 close 事件的监听器 */ + offClose: (fn: Callback) => void; + } + + interface CreateRewardedVideoAdOption { + /** 广告位 id */ + adUnitId: string; + /** 是否开启再得广告模式(只支持安卓系统的抖音和抖音极速版) */ + multiton?: boolean; + /** + * 再得广告的奖励文案,玩家每看完一个广告都会展示,如【再看1个获得xx】 + * xx 即 multitonRewardMsg 中的文案,按顺序依次展示,单个文案最大长度为 7 + * multiton 为 true 时必填 + */ + multitonRewardMsg?: string[]; + /** + * 额外观看广告的次数,合法的数据范围为 1-4,multiton 为 true 时必填 + */ + multitonRewardTimes?: number; + /** + * 是否开启进度提醒,开启时广告文案为【再看N个获得xx】,关闭时为【 再看1个获得xx】。 + * N 表示玩家当前还需额外观看广告的次数 + */ + progressTip?: boolean; + } + + interface IPaymentOptions { + /** 支付的类型, 目前仅为"game" */ + mode: "game"; + /** 环境配置,目前合法值仅为"0" */ + env: 0; + /** 货币类型,目前合法值仅为"CNY" */ + currencyType: "CNY"; + /** 申请接入时的平台,目前仅为"android" */ + platform: "android"; + /** + * 金币购买数量,金币数量必须满足:金币数量*金币单价 = 限定价格等级 + * goodType为游戏币场景时必传,其他场景不传 + */ + buyQuantity?: number; + + /** + * 游戏服务区id,开发者自定义。游戏不分大区则默认填写"1"。如果应用支持多角色,则角色 ID 接在分区 ID 后,用"_"连接 + */ + zoneId?: string; + /** + * 游戏开发者自定义的唯一订单号,订单支付成功后通过服务端支付结果回调回传 + * 必须具有唯一性,如果不传或重复传相同值,则会报错 + */ + customId: string; + /** 游戏开发者自定义的其他信息,订单支付成功后通过服务端支付结果回调回传。字符串长度最大不能超过 256。 */ + extraInfo?: string; + /** 支付场景 默认:0 */ + goodType?: number; + /** goodType为道具直购场景时必传,代表道具现金价格,单位为【分】,如道具价格为0.1元,则回传10 */ + orderAmount?: string; + /** goodType为道具直购场景时,代表道具名称,长度限制小于等于10个字符,用于区分道具类型 */ + goodName?: string; + + success?: (res: GeneralSuccessResult) => void; + fail?: (res: GeneralFailCodeResult) => void; + complete?: (res: any) => void; + } + + interface IAwemeCustomerOptions { + /** 游戏开发者自定义的其他信息,订单支付成功后通过服务端支付结果回调回传。字符串长度最大不能超过 256。(强烈建议传入) */ + extraInfo?: string; + /** + * 游戏服务区id,开发者自定义。游戏不分大区则默认填写"1"。如果应用支持多角色,则角色 ID 接在分区 ID 后,用"_"连接 + */ + zoneId?: string; + + /** 币种,目前仅为“DIAMOND” */ + currencyType: "DIAMOND" | "CNY"; + /** + * 金币购买数量,金币数量必须满足:金币数量*金币单价 = 限定价格等级 + * goodType为游戏币场景时必传,其他场景不传 + */ + buyQuantity?: number; + /** 支付场景 默认:0 */ + goodType?: number; + /** goodType为道具直购场景时必传,代表道具现金价格,单位为【分】,如道具价格为0.1元,则回传10 */ + orderAmount?: string; + /** goodType为道具直购场景时,代表道具名称,长度限制小于等于10个字符,用于区分道具类型 */ + goodName?: string; + /** + * 游戏开发者自定义的唯一订单号,订单支付成功后通过服务端支付结果回调回传 + * 必须具有唯一性,如果不传或重复传相同值,则会报错 + */ + customId: string; + success?: (res: GeneralSuccessResult) => void; + fail?: (res: GeneralFailResult) => void; + complete?: (res: any) => void; + } + + interface TT { + getEnvInfoSync(): EnvInfo; + getSystemInfoSync(): SystemInfo; + getLaunchOptionsSync(): LaunchParams; + exitMiniProgram(res: { + success?: GeneralSuccessCallback, + fail?: GeneralFailCallback, + complete?: GeneralCompleteCallback + }): void; + + setClipboardData(res: { + data: string, + success?: GeneralSuccessCallback, + fail?: GeneralFailCallback, + complete?: GeneralCompleteCallback + }): void; + + connectSocket(res: { + /** Socket 连接地址 */ + url: string; + /** 请求头 */ + header?: Record; + /** 子协议 */ + protocols?: string[]; + success?: (res: { socketTaskId: number }) => void, + fail?: GeneralFailCallback, + complete?: GeneralCompleteCallback + }): SocketTask; + + // requestMidasPayment(res: MidasPaymentOption): void; + + createRewardedVideoAd(option: CreateRewardedVideoAdOption): RewardedVideoAd; + + /** 支付 */ + requestGamePayment(res: IPaymentOptions): void; + /** 发起抖音钻石支付 */ + openAwemeCustomerService(res: IAwemeCustomerOptions): void; + } + +} +declare const tt: BytedanceMiniprogram.TT \ No newline at end of file diff --git a/libs/lib.wx.api.d.ts b/libs/lib.wx.api.d.ts index bedc302..9cccad6 100644 --- a/libs/lib.wx.api.d.ts +++ b/libs/lib.wx.api.d.ts @@ -24,6 +24,11 @@ declare namespace WechatMiniprogram { complete?: () => void; } + interface GeneralCallbackResult { + /** 错误信息 */ + errMsg: string + } + interface ConnectSocketOption extends ICommonCallBack { /** 开发者服务器 wss 接口地址 */ url: string @@ -148,6 +153,510 @@ declare namespace WechatMiniprogram { interface Wx { connectSocket(option: ConnectSocketOption): SocketTask } -} + /** 启动参数 */ + interface LaunchOptionsApp { + /** 需要基础库: `2.20.0` + * + * API 类别 + * + * 可选值: + * - 'default': 默认类别; + * - 'nativeFunctionalized': 原生功能化,视频号直播商品、商品橱窗等场景打开的小程序; + * - 'browseOnly': 仅浏览,朋友圈快照页等场景打开的小程序; + * - 'embedded': 内嵌,通过打开半屏小程序能力打开的小程序; */ + apiCategory: + | 'default' + | 'nativeFunctionalized' + | 'browseOnly' + | 'embedded' + /** 打开的文件信息数组,只有从聊天素材场景打开(scene为1173)才会携带该参数 */ + forwardMaterials: ForwardMaterials[] + /** 启动小程序的路径 (代码包路径) */ + path: string + /** 启动小程序的 query 参数 */ + query: Record + /** 来源信息。从另一个小程序、公众号或 App 进入小程序时返回。否则返回 `{}`。(参见后文注意) */ + referrerInfo: ReferrerInfo + /** 启动小程序的[场景值](https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/scene.html) */ + scene: number + /** 从微信群聊/单聊打开小程序时,chatType 表示具体微信群聊/单聊类型 + * + * 可选值: + * - 1: 微信联系人单聊; + * - 2: 企业微信联系人单聊; + * - 3: 普通微信群聊; + * - 4: 企业微信互通群聊; */ + chatType?: 1 | 2 | 3 | 4 + /** shareTicket,详见[获取更多转发信息](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html) */ + shareTicket?: string + } + + /** 当前小程序运行的宿主环境 */ + interface AppBaseInfoHost { + /** 宿主 app(第三方App) 对应的 appId (当小程序运行在第三方App环境时才返回) */ + appId: string + } + + interface AppBaseInfo { + /** 客户端基础库版本 */ + SDKVersion: string + /** 是否已打开调试。可通过右上角菜单或 [wx.setEnableDebug](https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.setEnableDebug.html) 打开调试。 */ + enableDebug: boolean + /** 当前小程序运行的宿主环境 */ + host: AppBaseInfoHost + /** 微信设置的语言 */ + language: string + /** 微信版本号 */ + version: string + /** 系统当前主题,取值为`light`或`dark`,全局配置`"darkmode":true`时才能获取,否则为 undefined (不支持小游戏) + * + * 可选值: + * - 'dark': 深色主题; + * - 'light': 浅色主题; */ + theme?: 'dark' | 'light' + } + + interface SafeArea { + /** 安全区域右下角纵坐标 */ + bottom: number + /** 安全区域的高度,单位逻辑像素 */ + height: number + /** 安全区域左上角横坐标 */ + left: number + /** 安全区域右下角横坐标 */ + right: number + /** 安全区域左上角纵坐标 */ + top: number + /** 安全区域的宽度,单位逻辑像素 */ + width: number + } + + interface WindowInfo { + /** 设备像素比 */ + pixelRatio: number + /** 在竖屏正方向下的安全区域。部分机型没有安全区域概念,也不会返回 safeArea 字段,开发者需自行兼容。 */ + safeArea: SafeArea + /** 屏幕高度,单位px */ + screenHeight: number + /** 窗口上边缘的y值 */ + screenTop: number + /** 屏幕宽度,单位px */ + screenWidth: number + /** 状态栏的高度,单位px */ + statusBarHeight: number + /** 可使用窗口高度,单位px */ + windowHeight: number + /** 可使用窗口宽度,单位px */ + windowWidth: number + } + + interface SystemInfo { + /** 需要基础库: `1.1.0` + * + * 客户端基础库版本 */ + SDKVersion: string + /** 需要基础库: `2.6.0` + * + * 允许微信使用相册的开关(仅 iOS 有效) */ + albumAuthorized: boolean + /** 需要基础库: `1.8.0` + * + * 设备性能等级(仅 Android)。取值为:-2 或 0(该设备无法运行小游戏),-1(性能未知),>=1(设备性能值,该值越高,设备性能越好)
注意:性能等级当前仅反馈真机机型,暂不支持 IDE 模拟器机型 */ + benchmarkLevel: number + /** 需要基础库: `2.6.0` + * + * 蓝牙的系统开关 */ + bluetoothEnabled: boolean + /** 需要基础库: `1.5.0` + * + * 设备品牌 */ + brand: string + /** 需要基础库: `2.6.0` + * + * 允许微信使用摄像头的开关 */ + cameraAuthorized: boolean + /** 设备方向 + * + * 可选值: + * - 'portrait': 竖屏; + * - 'landscape': 横屏; */ + deviceOrientation: 'portrait' | 'landscape' + /** 需要基础库: `2.15.0` + * + * 是否已打开调试。可通过右上角菜单或 [wx.setEnableDebug](https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.setEnableDebug.html) 打开调试。 */ + enableDebug: boolean + /** 需要基础库: `1.5.0` + * + * 用户字体大小(单位px)。以微信客户端「我-设置-通用-字体大小」中的设置为准 */ + fontSizeSetting: number + /** 需要基础库: `2.12.3` + * + * 当前小程序运行的宿主环境 */ + host: SystemInfoHost + /** 微信设置的语言 */ + language: string + /** 需要基础库: `2.6.0` + * + * 允许微信使用定位的开关 */ + locationAuthorized: boolean + /** 需要基础库: `2.6.0` + * + * 地理位置的系统开关 */ + locationEnabled: boolean + /** `true` 表示模糊定位,`false` 表示精确定位,仅 iOS 支持 */ + locationReducedAccuracy: boolean + /** 需要基础库: `2.6.0` + * + * 允许微信使用麦克风的开关 */ + microphoneAuthorized: boolean + /** 设备型号。新机型刚推出一段时间会显示unknown,微信会尽快进行适配。 */ + model: string + /** 需要基础库: `2.6.0` + * + * 允许微信通知带有提醒的开关(仅 iOS 有效) */ + notificationAlertAuthorized: boolean + /** 需要基础库: `2.6.0` + * + * 允许微信通知的开关 */ + notificationAuthorized: boolean + /** 需要基础库: `2.6.0` + * + * 允许微信通知带有标记的开关(仅 iOS 有效) */ + notificationBadgeAuthorized: boolean + /** 需要基础库: `2.6.0` + * + * 允许微信通知带有声音的开关(仅 iOS 有效) */ + notificationSoundAuthorized: boolean + /** 需要基础库: `2.19.3` + * + * 允许微信使用日历的开关 */ + phoneCalendarAuthorized: boolean + /** 设备像素比 */ + pixelRatio: number + /** 客户端平台 + * + * 可选值: + * - 'ios': iOS微信(包含 iPhone、iPad); + * - 'android': Android微信; + * - 'windows': Windows微信; + * - 'mac': macOS微信; + * - 'devtools': 微信开发者工具; */ + platform: 'ios' | 'android' | 'windows' | 'mac' | 'devtools' + /** 需要基础库: `2.7.0` + * + * 在竖屏正方向下的安全区域。部分机型没有安全区域概念,也不会返回 safeArea 字段,开发者需自行兼容。 */ + safeArea: SafeArea + /** 需要基础库: `1.1.0` + * + * 屏幕高度,单位px */ + screenHeight: number + /** 需要基础库: `1.1.0` + * + * 屏幕宽度,单位px */ + screenWidth: number + /** 需要基础库: `1.9.0` + * + * 状态栏的高度,单位px */ + statusBarHeight: number + /** 操作系统及版本 */ + system: string + /** 微信版本号 */ + version: string + /** 需要基础库: `2.6.0` + * + * Wi-Fi 的系统开关 */ + wifiEnabled: boolean + /** 可使用窗口高度,单位px */ + windowHeight: number + /** 可使用窗口宽度,单位px */ + windowWidth: number + /** 需要基础库: `2.11.0` + * + * 系统当前主题,取值为`light`或`dark`,全局配置`"darkmode":true`时才能获取,否则为 undefined (不支持小游戏) + * + * 可选值: + * - 'dark': 深色主题; + * - 'light': 浅色主题; */ + theme?: 'dark' | 'light' + } + + interface DeviceInfo { + /** 应用(微信APP)二进制接口类型(仅 Android 支持) */ + abi: string + /** 设备性能等级(仅 Android 支持)。取值为:-2 或 0(该设备无法运行小游戏),-1(性能未知),>=1(设备性能值,该值越高,设备性能越好,目前最高不到50) */ + benchmarkLevel: number + /** 设备品牌 */ + brand: string + /** 需要基础库: `2.29.0` + * + * 设备 CPU 型号(仅 Android 支持)(Tips: GPU 型号可通过 WebGLRenderingContext.getExtension('WEBGL_debug_renderer_info') 来获取) */ + cpuType: string + /** 需要基础库: `2.25.1` + * + * 设备二进制接口类型(仅 Android 支持) */ + deviceAbi: string + /** 需要基础库: `2.30.0` + * + * 设备内存大小,单位为 MB */ + memorySize: string + /** 设备型号。新机型刚推出一段时间会显示unknown,微信会尽快进行适配。 */ + model: string + /** 客户端平台 */ + platform: string + /** 操作系统及版本 */ + system: string + } + + /** 小程序账号信息 */ + interface MiniProgram { + /** 小程序 appId */ + appId: string + /** 需要基础库: `2.10.0` + * + * 小程序版本 + * + * 可选值: + * - 'develop': 开发版; + * - 'trial': 体验版; + * - 'release': 正式版; */ + envVersion: 'develop' | 'trial' | 'release' + /** 需要基础库: `2.10.2` + * + * 线上小程序版本号 */ + version: string + } + + /** 插件账号信息(仅在插件中调用时包含这一项) */ + interface Plugin { + /** 插件 appId */ + appId: string + /** 插件版本号 */ + version: string + } + + /** 账号信息 */ + interface AccountInfo { + /** 小程序账号信息 */ + miniProgram: MiniProgram + /** 插件账号信息(仅在插件中调用时包含这一项) */ + plugin: Plugin + } + + interface MidasPaymentOption { + /** 支付的类型,不同的支付类型有各自额外要传的附加参数 */ + mode: "game", + /** 是否为沙盒环境 0: 正式环境 1: 沙盒环境 */ + env?: 0 | 1, + /** 商户号 在米大师侧申请的应用id */ + offerId: string, + /** 货币类型 */ + currencyType: "CNY", + /** 申请接入时的平台,platform 与应用id有关 */ + platform?: "android" | "windows", + /** 购买数量。mode=game 时必填。购买数量 */ + buyQuantity: number, + /** 分区ID 默认1 */ + zoneId?: string, + /** + * 业务订单号,每个订单号只能使用一次,重复使用会失败。开发者需要确保该订单号在对应游戏下的唯一性,平台会尽可能校验该唯一性约束,但极端情况下可能会跳过对该约束的校验。要求32个字符内,只能是数字、大小写字母、符号_-|*组成,不能以下划线()开头。建议每次调用wx.requestMidasPayment都换新的outTradeNo。若没有传入,则平台会自动填充一个,并以下划线开头 + */ + outTradeNo: string, + /** 接口调用成功的回调函数 */ + success?: (res: { errMsg: string }) => void, + /** 接口调用失败的回调函数 */ + fail?: (res: { errCode: number, errMsg: string, errno: number }) => void, + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete?: () => void; + } + + interface RewardedVideoAdOnCloseListenerResult { + /** 需要基础库: `2.1.0` + * + * 视频是否是在用户完整观看的情况下被关闭的 */ + isEnded: boolean + } + interface RewardedVideoAdOnErrorListenerResult { + /** 需要基础库: `2.2.2` + * + * 错误码 + * + * 可选值: + * - 1000: 后端接口调用失败; + * - 1001: 参数错误; + * - 1002: 广告单元无效; + * - 1003: 内部错误; + * - 1004: 无合适的广告; + * - 1005: 广告组件审核中; + * - 1006: 广告组件被驳回; + * - 1007: 广告组件被封禁; + * - 1008: 广告单元已关闭; */ + errCode: 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 + /** 错误信息 */ + errMsg: string + } + + /** onClose 传入的监听函数。不传此参数则移除所有监听函数。 */ + type RewardedVideoAdOffCloseCallback = ( + result: RewardedVideoAdOnCloseListenerResult + ) => void + /** onError 传入的监听函数。不传此参数则移除所有监听函数。 */ + type RewardedVideoAdOffErrorCallback = ( + result: RewardedVideoAdOnErrorListenerResult + ) => void + /** 用户点击 `关闭广告` 按钮的事件的监听函数 */ + type RewardedVideoAdOnCloseCallback = ( + result: RewardedVideoAdOnCloseListenerResult + ) => void + /** 激励视频错误事件的监听函数 */ + type RewardedVideoAdOnErrorCallback = ( + result: RewardedVideoAdOnErrorListenerResult + ) => void + /** onLoad 传入的监听函数。不传此参数则移除所有监听函数。 */ + type OffLoadCallback = (res: GeneralCallbackResult) => void + type OnLoadCallback = (res: GeneralCallbackResult) => void + + interface RewardedVideoAd { + /** + * [Promise RewardedVideoAd.load()](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.load.html) + * 在插件中使用:不支持 + * 加载激励视频广告。 */ + load(): Promise + /** + * [Promise RewardedVideoAd.show()](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.show.html) + * 在插件中使用:不支持 + * 显示激励视频广告。激励视频广告将从屏幕下方推入。 */ + show(): Promise + /** + * [RewardedVideoAd.destroy()](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.destroy.html) + * 需要基础库: `2.8.0` + * 在插件中使用:不支持 + * 销毁激励视频广告实例。 */ + destroy(): void + /** + * [RewardedVideoAd.offClose(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.offClose.html) + * 在插件中使用:不支持 + * 移除用户点击 `关闭广告` 按钮的事件的监听函数 + * + * **示例代码** + * + * ```js + * const listener = function (res) { console.log(res) } + * + * RewardedVideoAd.onClose(listener) + * RewardedVideoAd.offClose(listener) // 需传入与监听时同一个的函数对象 + * ``` + */ + offClose( + /** onClose 传入的监听函数。不传此参数则移除所有监听函数。 */ + listener?: RewardedVideoAdOffCloseCallback + ): void + /** + * [RewardedVideoAd.offError(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.offError.html) + * 在插件中使用:不支持 + * 移除激励视频错误事件的监听函数 + * + * **示例代码** + * + * ```js + * const listener = function (res) { console.log(res) } + * + * RewardedVideoAd.onError(listener) + * RewardedVideoAd.offError(listener) // 需传入与监听时同一个的函数对象 + * ``` + */ + offError( + /** onError 传入的监听函数。不传此参数则移除所有监听函数。 */ + listener?: RewardedVideoAdOffErrorCallback + ): void + /** + * [RewardedVideoAd.offLoad(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.offLoad.html) + * 在插件中使用:不支持 + * 移除激励视频广告加载事件的监听函数 + * **示例代码** + * ```js + * const listener = function (res) { console.log(res) } + * + * RewardedVideoAd.onLoad(listener) + * RewardedVideoAd.offLoad(listener) // 需传入与监听时同一个的函数对象 + * ``` + */ + offLoad( + /** onLoad 传入的监听函数。不传此参数则移除所有监听函数。 */ + listener?: OffLoadCallback + ): void + /** + * [RewardedVideoAd.onClose(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.onClose.html) + * 在插件中使用:不支持 + * 监听用户点击 `关闭广告` 按钮的事件。 */ + onClose( + /** 用户点击 `关闭广告` 按钮的事件的监听函数 */ + listener: RewardedVideoAdOnCloseCallback + ): void + /** + * [RewardedVideoAd.onError(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.onError.html) + * 在插件中使用:不支持 + * 监听激励视频错误事件。 + * + * **错误码信息与解决方案表** + * + * 错误码是通过onError获取到的错误信息。调试期间,可以通过异常返回来捕获信息。 + * 在小程序发布上线之后,如果遇到异常问题,可以在[“运维中心“](https://mp.weixin.qq.com/)里面搜寻错误日志,还可以针对异常返回加上适当的监控信息。 + * + * | 代码 | 异常情况 | 理由 | 解决方案 | + * | ------ | -------------- | --------------- | -------------------------- | + * | 1000 | 后端错误调用失败 | 该项错误不是开发者的异常情况 | 一般情况下忽略一段时间即可恢复。 | + * | 1001 | 参数错误 | 使用方法错误 | 可以前往developers.weixin.qq.com确认具体教程(小程序和小游戏分别有各自的教程,可以在顶部选项中,“设计”一栏的右侧进行切换。| + * | 1002 | 广告单元无效 | 可能是拼写错误、或者误用了其他APP的广告ID | 请重新前往mp.weixin.qq.com确认广告位ID。 | + * | 1003 | 内部错误 | 该项错误不是开发者的异常情况 | 一般情况下忽略一段时间即可恢复。| + * | 1004 | 无适合的广告 | 广告不是每一次都会出现,这次没有出现可能是由于该用户不适合浏览广告 | 属于正常情况,且开发者需要针对这种情况做形态上的兼容。 | + * | 1005 | 广告组件审核中 | 你的广告正在被审核,无法展现广告 | 请前往mp.weixin.qq.com确认审核状态,且开发者需要针对这种情况做形态上的兼容。| + * | 1006 | 广告组件被驳回 | 你的广告审核失败,无法展现广告 | 请前往mp.weixin.qq.com确认审核状态,且开发者需要针对这种情况做形态上的兼容。| + * | 1007 | 广告组件被封禁 | 你的广告能力已经被封禁,封禁期间无法展现广告 | 请前往mp.weixin.qq.com确认小程序广告封禁状态。 | + * | 1008 | 广告单元已关闭 | 该广告位的广告能力已经被关闭 | 请前往mp.weixin.qq.com重新打开对应广告位的展现。| */ + onError( + /** 激励视频错误事件的监听函数 */ + listener: RewardedVideoAdOnErrorCallback + ): void + /** + * [RewardedVideoAd.onLoad(function listener)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.onLoad.html) + * 在插件中使用:不支持 + * 监听激励视频广告加载事件。 */ + onLoad( + /** 激励视频广告加载事件的监听函数 */ + listener: OnLoadCallback + ): void + } + + interface CreateRewardedVideoAdOption { + /** 广告单元 id */ + adUnitId: string + /** + * 需要基础库: `2.8.0` + * 是否启用多例模式,默认为false + */ + multiton?: boolean + } + + interface Wx { + getLaunchOptionsSync(): LaunchOptionsApp; + getEnterOptionsSync(): LaunchOptionsApp; + getWindowInfo(): WindowInfo; + getAppBaseInfo(): AppBaseInfo; + getSystemInfoSync(): SystemInfo; + getDeviceInfo(): DeviceInfo; + getAccountInfoSync(): AccountInfo; + exitMiniProgram(): void; + setClipboardData(res: { data: string, fail: (res: GeneralCallbackResult) => void }): void; + requestMidasPayment(res: MidasPaymentOption): void; + /** + * [[RewardedVideoAd](https://developers.weixin.qq.com/miniprogram/dev/api/ad/RewardedVideoAd.html) wx.createRewardedVideoAd(Object object)](https://developers.weixin.qq.com/miniprogram/dev/api/ad/wx.createRewardedVideoAd.html) + * 需要基础库: `2.0.4` + * 在插件中使用:需要基础库 `2.8.1` + * + * 创建激励视频广告组件。请通过 [wx.getSystemInfoSync()](https://developers.weixin.qq.com/miniprogram/dev/api/base/system/wx.getSystemInfoSync.html) 返回对象的 SDKVersion 判断基础库版本号后再使用该 API(小游戏端要求 >= 2.0.4, 小程序端要求 >= 2.6.0)。调用该方法创建的激励视频广告是一个单例(小游戏端是全局单例,小程序端是页面内单例,在小程序端的单例对象不允许跨页面使用)。 + */ + createRewardedVideoAd(option: CreateRewardedVideoAdOption): RewardedVideoAd + } +} declare const wx: WechatMiniprogram.Wx \ No newline at end of file diff --git a/src/hotupdate/HotUpdateManager.ts b/src/hotupdate/HotUpdateManager.ts index 20f25b9..32c89e3 100644 --- a/src/hotupdate/HotUpdateManager.ts +++ b/src/hotupdate/HotUpdateManager.ts @@ -7,6 +7,7 @@ import { Asset, game, native, sys } from "cc"; import { Platform } from "../global/Platform"; import { log, warn } from "../tool/log"; +import { Utils } from "../tool/Utils"; const TAG = "hotupdate:"; @@ -60,7 +61,7 @@ export class HotUpdateManager { // 创建 am 对象 this._am = native.AssetsManager.create("", this._writablePath); - this._am.setVersionCompareHandle(this._versionCompareHandle); + this._am.setVersionCompareHandle(Utils.compareVersion); this._am.setVerifyCallback(this._verifyCallback); // 加载本地的 manifest log(`${TAG} 加载本地的 manifest:${manifest.nativeUrl}`); @@ -223,33 +224,6 @@ export class HotUpdateManager { } } - /** - * 版本号比较 - * @param version1 本地版本号 - * @param version2 远程版本号 - * 如果返回值大于0,则version1大于version2 - * 如果返回值等于0,则version1等于version2 - * 如果返回值小于0,则version1小于version2 - */ - private _versionCompareHandle(version1: string, version2: string): number { - log(`${TAG}本地资源版本号:${version1} 远程资源版本号:${version2}`); - let v1 = version1.split('.'); - let v2 = version2.split('.'); - for (let i = 0; i < v1.length; ++i) { - let a = parseInt(v1[i]); - let b = parseInt(v2[i] || '0'); - if (a === b) { - continue; - } else { - return a - b; - } - } - if (v2.length > v1.length) { - return -1; - } - return 0; - } - private _verifyCallback(path: string, asset: native.ManifestAsset): boolean { // 资源是否被压缩, 如果压缩我们不需要检查它的md5值 let compressed = asset.compressed; diff --git a/src/kunpocc.ts b/src/kunpocc.ts index 5434e3a..25f0df2 100644 --- a/src/kunpocc.ts +++ b/src/kunpocc.ts @@ -79,3 +79,10 @@ export { ConditionBase } from "./condition/node/ConditionBase"; /** 热更新 */ export { HotUpdateManager } from "./hotupdate/HotUpdateManager"; + +/** 小游戏 */ +export { AlipayCommon } from "./minigame/alipay/AlipayCommon"; +export { BytedanceCommon } from "./minigame/bytedance/BytedanceCommon"; +export { MiniHelper } from "./minigame/MiniHelper"; +export { WechatCommon } from "./minigame/wechat/WechatCommon"; + diff --git a/src/minigame/MiniHelper.ts b/src/minigame/MiniHelper.ts new file mode 100644 index 0000000..6927832 --- /dev/null +++ b/src/minigame/MiniHelper.ts @@ -0,0 +1,68 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 小游戏辅助类 + */ + +import { Platform } from "../global/Platform"; +import { BytedanceCommon } from "../kunpocc"; +import { AlipayAds } from "./alipay/AlipayAds"; +import { AlipayCommon } from "./alipay/AlipayCommon"; +import { AlipayPay } from "./alipay/AlipayPay"; +import { BytedanceAds } from "./bytedance/BytedanceAds"; +import { BytedancePay } from "./bytedance/BytedancePay"; +import { IMiniRewardAds } from "./interface/IMiniAds"; +import { IMiniCommon } from "./interface/IMiniCommon"; +import { IMiniPay } from "./interface/IMiniPay"; +import { WechatAds } from "./wechat/WechatAds"; +import { WechatCommon } from "./wechat/WechatCommon"; +import { WechatPay } from "./wechat/WechatPay"; + +export class MiniHelper { + /** 基础数据 */ + private static _common: IMiniCommon = null; + /** 广告 */ + private static _ad: IMiniRewardAds = null; + /** 支付 */ + private static _pay: IMiniPay = null; + + public static common(): T { + if (!this._common) { + if (Platform.isWX) { + this._common = new WechatCommon(); + } else if (Platform.isAlipay) { + this._common = new AlipayCommon(); + this._ad = new AlipayAds(); + } else if (Platform.isBytedance) { + this._common = new BytedanceCommon(); + } + } + return this._common as T; + } + + public static ad(): T { + if (!this._ad) { + if (Platform.isWX) { + this._ad = new WechatAds(); + } else if (Platform.isAlipay) { + this._ad = new AlipayAds(); + } else if (Platform.isBytedance) { + this._ad = new BytedanceAds(); + } + } + return this._ad as T; + } + + public static pay(): T { + if (!this._pay) { + if (Platform.isWX) { + this._pay = new WechatPay(); + } else if (Platform.isAlipay) { + this._pay = new AlipayPay(); + } else if (Platform.isBytedance) { + this._pay = new BytedancePay(); + } + } + return this._pay as T; + } +} \ No newline at end of file diff --git a/src/minigame/alipay/AlipayAds.ts b/src/minigame/alipay/AlipayAds.ts new file mode 100644 index 0000000..19420db --- /dev/null +++ b/src/minigame/alipay/AlipayAds.ts @@ -0,0 +1,75 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 支付宝广告 + */ + +import { warn } from "../../tool/log"; +import { MiniErrorCode } from "../header"; +import { IMiniRewardAds } from "../interface/IMiniAds"; + +export class AlipayAds implements IMiniRewardAds { + private _adUnitId: string = ""; + private _video_ad: AliyMiniprogram.RewardedAd = null; + + /** + * 广告成功回调 + */ + private _success: () => void; + /** + * 广告失败回调 + */ + private _fail: (errCode: number, errMsg: string) => void; + + public init(adUnitId: string): void { + this._adUnitId = adUnitId; + } + + public showAds(res: { success: () => void, fail: (errCode: number, errMsg: string) => void }): void { + if (this._adUnitId === "") { + warn(MiniErrorCode.AD_NOT_INIT.msg); + res.fail(MiniErrorCode.AD_NOT_INIT.code, MiniErrorCode.AD_NOT_INIT.msg); + return; + } + this._success = res.success; + this._fail = res.fail; + + if (!this._video_ad) { + this._video_ad = this.createVideoAd(); + } + this._video_ad.load().then(() => { + this._video_ad.show(); + }).catch((res: { errMsg: string; errNo: number }) => { + this._fail(res.errNo, res.errMsg); + this.reset(); + }); + } + + private createVideoAd(): AliyMiniprogram.RewardedAd { + let videoAd = my.createRewardedAd({ adUnitId: this._adUnitId, multiton: false }); + /** 广告加载失败 */ + videoAd.onError((res: AliyMiniprogram.CallBack.Fail) => { + this._fail?.(res.error, res.errorMessage); + this.reset(); + }); + + videoAd.onClose((res: { isEnded: boolean }) => { + if ((res && res.isEnded) || res === undefined) { + /** 广告播放完成 */ + this?._success(); + this.reset(); + } else { + /** 中途退出,不发放奖励 */ + this?._fail(MiniErrorCode.AD_EXIT.code, MiniErrorCode.AD_EXIT.msg); + this.reset(); + } + }); + return videoAd; + } + + /** 防止多次回调 */ + private reset(): void { + this._success = null; + this._fail = null; + } +} diff --git a/src/minigame/alipay/AlipayCommon.ts b/src/minigame/alipay/AlipayCommon.ts new file mode 100644 index 0000000..ceb45e6 --- /dev/null +++ b/src/minigame/alipay/AlipayCommon.ts @@ -0,0 +1,119 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 支付宝小游戏工具类 + */ + +import { warn } from "../../tool/log"; +import { IMiniCommon } from "../interface/IMiniCommon"; + + +export class AlipayCommon implements IMiniCommon { + private _launchOptions: AliyMiniprogram.AppLaunchOptions = null; + + private _systemInfo: getSystemInfoSyncReturn = null; + private _accountInfo: AliyMiniprogram.AccountInfo = null; + + /** + * @internal + */ + constructor() { + this._launchOptions = my.getLaunchOptionsSync(); + } + + public getLaunchOptions(): AliyMiniprogram.AppLaunchOptions { + return this._launchOptions; + } + + public getHotLaunchOptions(): Record { + return my.getEnterOptionsSync(); + } + + /** + * 获取基础库版本号 + */ + public getLibVersion(): string { + return my.SDKVersion; + } + + /** + * 获取运行平台 合法值(ios | android | ohos | windows | mac | devtools) + */ + public getPlatform(): 'ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools' | 'iPad' { + let platform = this.getSystemInfo().platform; + if (platform === 'iOS' || platform == 'iPhone OS') { + return 'ios'; + } else if (platform.indexOf('iPad') > 0) { + return 'iPad'; + } + return platform as ('ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools' | 'iPad'); + } + + /** + * 获取版本类型 + */ + public getEnvType(): 'release' | 'debug' { + return this.getAccountInfo().miniProgram.envVersion == "release" ? "release" : "debug"; + } + + /** + * 宿主程序版本 (这里指支付宝 或其他宿主 版本) + */ + public getHostVersion(): string { + return this.getSystemInfo().version; + } + + /** + * 获取屏幕尺寸 + */ + public getScreenSize(): { width: number, height: number } { + const systemInfo = this.getSystemInfo(); + return { + width: systemInfo.windowWidth, + height: systemInfo.windowHeight + } + } + + /** + * 退出当前小程序 (必须通过点击事件触发才能调用成功) + */ + public exitMiniProgram(): void { + my.exitProgram(); + } + + /** + * 复制到剪切板 + */ + public setClipboardData(text: string): void { + my.setClipboard({ + text: text, + fail: (res: AliyMiniprogram.CallBack.Fail) => { + warn(`复制到剪切板失败 code:${res.error} msg:${res.errorMessage}`); + } + }); + } + + private getSystemInfo(): getSystemInfoSyncReturn { + if (this._systemInfo) { + return this._systemInfo; + } + if (my.getSystemInfoSync) { + this._systemInfo = my.getSystemInfoSync(); + return this._systemInfo; + } + warn("getSystemInfo 失败"); + return null; + } + + private getAccountInfo(): AliyMiniprogram.AccountInfo { + if (this._accountInfo) { + return this._accountInfo; + } + if (my.getAccountInfoSync) { + this._accountInfo = my.getAccountInfoSync(); + return this._accountInfo; + } + warn("getAccountInfo 失败"); + return null; + } +} \ No newline at end of file diff --git a/src/minigame/alipay/AlipayPay.ts b/src/minigame/alipay/AlipayPay.ts new file mode 100644 index 0000000..1e5c388 --- /dev/null +++ b/src/minigame/alipay/AlipayPay.ts @@ -0,0 +1,72 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 支付宝支付 + */ + +import { log, warn } from "../../tool/log"; +import { Utils } from "../../tool/Utils"; +import { MiniErrorCode, PriceLimitList } from "../header"; +import { IMiniPay, IMiniPayParams } from "../interface/IMiniPay"; +import { MiniHelper } from "../MiniHelper"; + +export class AlipayPay implements IMiniPay { + private _unitPriceQuantity: number = 0; + public init(offerId: string, unitPriceQuantity: number): void { + this._unitPriceQuantity = unitPriceQuantity; + } + + public isPayable(rmb: number): boolean { + return PriceLimitList.includes(rmb); + } + + public pay(res: IMiniPayParams): void { + if (this._unitPriceQuantity === 0) { + warn("请先调用 init 方法初始化"); + res.fail({ errCode: MiniErrorCode.PAY_NOT_INIT.code, errMsg: MiniErrorCode.PAY_NOT_INIT.msg }); + return; + } + if (!this.isPayable(res.rmb)) { + res.fail({ errCode: -15016, errMsg: "传入价格不满足限定条件" }); + return; + } + + let platform = MiniHelper.common().getPlatform(); + if (platform === "ios") { + res.fail({ errCode: MiniErrorCode.IOS_FORBIDDEN.code, errMsg: MiniErrorCode.IOS_FORBIDDEN.msg }); + return; + } + if (platform === "iPad") { + res.fail({ errCode: MiniErrorCode.IOS_FORBIDDEN.code, errMsg: "iPad禁止支付" }); + return; + } + + if (Utils.compareVersion(MiniHelper.common().getHostVersion(), "10.3.90") < 0) { + res.fail({ errCode: MiniErrorCode.IOS_FORBIDDEN.code, errMsg: "支付宝版本过低, 请升级支付宝" }); + return; + } + + log(`AlipayPay rmb:${res.rmb}元 orderId:${res.orderId} sandbox:${res.sandbox || 0} shopId:${res.shopId} shopName:${res.shopName}`); + let extraInfo = { + shopId: res.shopId, + shopName: res.shopName, + sandbox: res.sandbox || 0, + } + if (res.extraInfo) { + // 合并extraInfo和res.extraInfo + extraInfo = { ...extraInfo, ...res.extraInfo }; + } + my.requestGamePayment({ + customId: res.orderId, + buyQuantity: res.rmb * this._unitPriceQuantity, + extraInfo: extraInfo, + success: (param: { resultCode: number }) => { + res.success({ code: param.resultCode, message: "success" }); + }, + fail: (param: AliyMiniprogram.CallBack.Fail) => { + warn(`WechatPay fail code:${param.error} msg:${param.errorMessage}`); + res.fail({ errCode: param.error, errMsg: param.errorMessage }); + } + }); + } +} \ No newline at end of file diff --git a/src/minigame/bytedance/BytedanceAds.ts b/src/minigame/bytedance/BytedanceAds.ts new file mode 100644 index 0000000..d456628 --- /dev/null +++ b/src/minigame/bytedance/BytedanceAds.ts @@ -0,0 +1,81 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 字节跳动广告 + */ + +import { log, warn } from "../../tool/log"; +import { MiniErrorCode } from "../header"; +import { IMiniRewardAds } from "../interface/IMiniAds"; + +export class BytedanceAds implements IMiniRewardAds { + private _adUnitId: string = ""; + private _video_ad: BytedanceMiniprogram.RewardedVideoAd = null; + + /** + * 广告成功回调 + */ + private _success: () => void; + /** + * 广告失败回调 + */ + private _fail: (errCode: number, errMsg: string) => void; + + public init(adUnitId: string): void { + this._adUnitId = adUnitId; + } + + /** + * 显示广告 + */ + public showAds(res: { success: () => void, fail: (errCode: number, errMsg: string) => void }): void { + if (this._adUnitId === "") { + warn(MiniErrorCode.AD_NOT_INIT.msg); + res.fail(MiniErrorCode.AD_NOT_INIT.code, MiniErrorCode.AD_NOT_INIT.msg); + return; + } + this._success = res.success; + this._fail = res.fail; + + if (!this._video_ad) { + this._video_ad = this.createVideoAd(); + } + log("加载广告"); + this._video_ad.load().then(() => { + log("广告加载成功"); + this._video_ad.show(); + }).catch((res: { errMsg: string; errNo: number }) => { + warn(`广告加载失败 errCode:${res.errNo} errMsg:${res.errMsg}`); + this._fail(res.errNo, res.errMsg); + this.reset(); + }); + } + + private createVideoAd(): BytedanceMiniprogram.RewardedVideoAd { + let videoAd = tt.createRewardedVideoAd({ adUnitId: this._adUnitId, multiton: false }); + /** 激励视频错误事件的监听函数 */ + videoAd.onError((res: { errMsg: string; errCode: number }) => { + warn(`激励视频广告 onError:${res.errCode}:${res.errMsg}`); + this._fail(res.errCode, res.errMsg); + this.reset(); + }); + videoAd.onClose((res: { isEnded: boolean, count?: number }) => { + if ((res && res.isEnded) || res === undefined) { + /** 广告播放完成 */ + this?._success(); + this.reset(); + } else { + /** 中途退出,不发放奖励 */ + this?._fail(MiniErrorCode.AD_EXIT.code, MiniErrorCode.AD_EXIT.msg); + this.reset(); + } + }); + return videoAd; + } + + /** 防止多次回调 */ + private reset(): void { + this._success = null; + this._fail = null; + } +} \ No newline at end of file diff --git a/src/minigame/bytedance/BytedanceCommon.ts b/src/minigame/bytedance/BytedanceCommon.ts new file mode 100644 index 0000000..b40563d --- /dev/null +++ b/src/minigame/bytedance/BytedanceCommon.ts @@ -0,0 +1,125 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 字节跳动小游戏工具类 + */ + +import { LaunchParams } from "@douyin-microapp/typings/types/app"; +import { warn } from "../../tool/log"; +import { IMiniCommon } from "../interface/IMiniCommon"; + +export class BytedanceCommon implements IMiniCommon { + private _launchOptions: BytedanceMiniprogram.LaunchParams = null; + + private _systemInfo: BytedanceMiniprogram.SystemInfo = null; + private _envInfo: BytedanceMiniprogram.EnvInfo = null; + + /** + * @internal + */ + constructor() { + this._launchOptions = tt.getLaunchOptionsSync(); + } + + /** + * 获取冷启动参数 + */ + public getLaunchOptions(): LaunchParams { + return this._launchOptions; + } + + /** + * 获取热启动参数 + */ + public getHotLaunchOptions(): LaunchParams { + warn("字节跳动小游戏未提供热启动参数获取方式,请在 onShow 中获取"); + return null; + } + + /** + * 获取基础库版本号 + */ + public getLibVersion(): string { + return this.getSystemInfo()?.SDKVersion || "0.0.1"; + } + + /** + * 宿主程序版本 (这里指今日头条、抖音等版本) + */ + public getHostVersion(): string { + return this.getSystemInfo()?.version || "0.0.1"; + } + + /** + * 宿主 APP 名称。示例:"Toutiao" + * 见 [https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/api/system/system-information/tt-get-system-info-sync] + */ + public getHostName(): string { + return this.getSystemInfo()?.appName || "unknown"; + } + + /** + * 获取运行平台 + */ + public getPlatform(): 'ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools' { + return this.getSystemInfo().platform as ('ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools'); + } + + /** + * 获取版本类型 + */ + public getEnvType(): 'release' | 'debug' { + return this.getEnvInfo().microapp.envType == "production" ? "release" : "debug"; + } + + /** + * 退出小程序 + */ + public exitMiniProgram(): void { + tt.exitMiniProgram?.({}); + } + + public getScreenSize(): { width: number, height: number } { + const systemInfo = this.getSystemInfo(); + return { + width: systemInfo.screenWidth, + height: systemInfo.screenHeight, + }; + } + + /** + * 复制到剪切板 + */ + public setClipboardData(text: string): void { + tt.setClipboardData({ + data: text, + fail: (res: { errMsg: string, errNo?: number }) => { + warn(`复制到剪切板失败 errCode:${res.errNo} errMsg:${res.errMsg}`); + } + }); + } + + private getEnvInfo(): BytedanceMiniprogram.EnvInfo { + if (this._envInfo) { + return this._envInfo; + } + if (tt.getEnvInfoSync) { + this._envInfo = tt.getEnvInfoSync(); + return this._envInfo; + } + warn("getEnvInfo 失败"); + return null; + } + + private getSystemInfo(): BytedanceMiniprogram.SystemInfo { + if (this._systemInfo) { + return this._systemInfo; + } + if (tt.getSystemInfoSync) { + this._systemInfo = tt.getSystemInfoSync(); + return this._systemInfo; + } + warn("getSystemInfo 失败"); + return null; + } +} \ No newline at end of file diff --git a/src/minigame/bytedance/BytedancePay.ts b/src/minigame/bytedance/BytedancePay.ts new file mode 100644 index 0000000..dfc8282 --- /dev/null +++ b/src/minigame/bytedance/BytedancePay.ts @@ -0,0 +1,125 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-12 + * @Description: 抖音支付 + * https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/api/payment/tt-request-game-payment + */ + +import { log, warn } from "../../tool/log"; +import { Utils } from "../../tool/Utils"; +import { MiniErrorCode, PriceLimitList } from "../header"; +import { IMiniPay, IMiniPayParams } from "../interface/IMiniPay"; +import { MiniHelper } from "../MiniHelper"; +import { BytedanceCommon } from "./BytedanceCommon"; + + +enum DouyinMiniAppName { + Toutiao = "Toutiao", // 今日头条 + Douyin = "Douyin", // 抖音 + ToutiaoLite = "news_article_lite", // 今日头条极速版 + LiveStream = "live_stream", // 火山小视频 + XiGua = "XiGua", // 西瓜 + PPX = "PPX", // 皮皮虾 + DouyinLite = "DouyinLite", // 抖音极速版 + LiveStreamLite = "live_stream_lite",// 火山小视频极速版 + NovelFm = "novel_fm", // 番茄畅听 + NovelApp = "novelapp", // 番茄小说 +} + +export class BytedancePay implements IMiniPay { + private _unitPriceQuantity: number = 0; + public init(offerId: string, unitPriceQuantity: number): void { + this._unitPriceQuantity = unitPriceQuantity; + } + + public isPayable(rmb: number): boolean { + return PriceLimitList.includes(rmb); + } + + public pay(res: IMiniPayParams): void { + if (this._unitPriceQuantity === 0) { + warn("请先调用 init 方法初始化"); + res.fail({ errCode: MiniErrorCode.PAY_NOT_INIT.code, errMsg: MiniErrorCode.PAY_NOT_INIT.msg }); + return; + } + if (!this.isPayable(res.rmb)) { + res.fail({ errCode: -15016, errMsg: "传入价格不满足限定条件" }); + return; + } + let platform = MiniHelper.common().getPlatform(); + log(`BytedancePay rmb:${res.rmb}元 orderId:${res.orderId} sandbox:${res.sandbox || 0} shopId:${res.shopId} shopName:${res.shopName}`); + if (platform === "android") { + this.payAndroid(res); + } else if (platform === "ios") { + this.payIos(res); + } else { + res.fail({ errCode: MiniErrorCode.PAY_NOT_IMPLEMENTED.code, errMsg: `${MiniErrorCode.IOS_FORBIDDEN.msg} platform:${platform}` }); + } + } + + private payAndroid(res: IMiniPayParams): void { + let extraInfo: Record = { + shopId: res.shopId, + shopName: res.shopName, + sandbox: res.sandbox || 0, + } + for (let key in res.extraInfo) { + extraInfo[key] = res.extraInfo[key]; + } + log("扩展参数:", JSON.stringify(extraInfo));; + tt.requestGamePayment({ + mode: 'game', + env: 0, + currencyType: 'CNY', + platform: 'android', + buyQuantity: Math.floor(res.rmb * this._unitPriceQuantity), + zoneId: '1', + customId: res.orderId, + extraInfo: JSON.stringify(extraInfo), + success: (param: BytedanceMiniprogram.GeneralSuccessResult) => { + res.success({ code: 0, message: param.errMsg }); + }, + fail: (param: BytedanceMiniprogram.GeneralFailCodeResult) => { + warn(`BytedancePay fail code:${param.errCode} msg:${param.errMsg}`); + res.fail({ errCode: param.errCode, errMsg: param.errMsg }); + } + }); + } + + private payIos(res: IMiniPayParams): void { + let appname = MiniHelper.common().getHostName(); + if (appname != DouyinMiniAppName.Douyin && appname != DouyinMiniAppName.DouyinLite) { + res.fail({ errCode: MiniErrorCode.PAY_NOT_IMPLEMENTED.code, errMsg: `${MiniErrorCode.PAY_NOT_IMPLEMENTED.msg} 宿主:${appname}` }); + return; + } + if (!tt.openAwemeCustomerService || Utils.compareVersion(MiniHelper.common().getLibVersion(), "2.64.0") < 0) { + res.fail({ errCode: MiniErrorCode.VERSION_LOW.code, errMsg: "抖音版本号过低,请升级后再试" }); + return; + } + let extraInfo = { + shopId: res.shopId, + shopName: res.shopName, + sandbox: res.sandbox || 0, + } + if (res.extraInfo) { + // 合并extraInfo和res.extraInfo + extraInfo = { ...extraInfo, ...res.extraInfo }; + } + tt.openAwemeCustomerService({ + currencyType: "CNY", + buyQuantity: Math.floor(res.rmb * this._unitPriceQuantity), + zoneId: '1', + /** 游戏唯一订单号 */ + customId: res.orderId, + /** 游戏开发者自定义的其他信息 字符串长度最大不能超过 256。*/ + extraInfo: JSON.stringify(extraInfo), + success: (params: BytedanceMiniprogram.GeneralSuccessResult) => { + res.success({ code: 0, message: params.errMsg }); + }, + fail: (params: BytedanceMiniprogram.GeneralFailResult) => { + warn(`BytedancePay fail code:${params.errNo} msg:${params.errMsg}`); + res.fail({ errCode: params.errNo, errMsg: params.errMsg }); + } + }); + } +} \ No newline at end of file diff --git a/src/minigame/header.ts b/src/minigame/header.ts new file mode 100644 index 0000000..7f85289 --- /dev/null +++ b/src/minigame/header.ts @@ -0,0 +1,24 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: + */ + +/** 记录一些错误码 */ +export const MiniErrorCode = { + /** 支付未初始化 */ + PAY_NOT_INIT: { code: -96001, msg: "支付未初始化" }, + /** ios禁止支付 */ + IOS_FORBIDDEN: { code: -96002, msg: "ios禁止支付" }, + /** 当前平台未实现支付 */ + PAY_NOT_IMPLEMENTED: { code: -96003, msg: "当前平台未实现支付" }, + /** 版本号低 */ + VERSION_LOW: { code: -96004, msg: "版本号过低" }, + /** 广告未初始化 */ + AD_NOT_INIT: { code: -97001, msg: "广告未初始化, 需要先调用init方法初始化" }, + /** 广告中途退出*/ + AD_EXIT: { code: -97002, msg: "广告中途退出" }, +} + +/** 统一价格限制列表 (微信、支付宝和字节 取交集) */ +export const PriceLimitList = [1, 3, 6, 8, 12, 18, 25, 30, 40, 45, 50, 60, 68, 73, 78, 88, 98, 108, 118, 128, 148, 168, 188, 198, 328, 648, 998, 1998, 2998]; \ No newline at end of file diff --git a/src/minigame/interface/IMiniAds.ts b/src/minigame/interface/IMiniAds.ts new file mode 100644 index 0000000..05f908b --- /dev/null +++ b/src/minigame/interface/IMiniAds.ts @@ -0,0 +1,20 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 小游戏广告接口 + */ + +/** 激励视频广告 */ +export interface IMiniRewardAds { + /** + * 广告初始化 + * @param adUnitId 广告位ID + * 不启用多广告实例 + */ + init(adUnitId: string): void; + + /** + * 显示广告 + */ + showAds(res: { success: () => void, fail: (errCode: number, errMsg: string) => void }): void; +} \ No newline at end of file diff --git a/src/minigame/interface/IMiniCommon.ts b/src/minigame/interface/IMiniCommon.ts new file mode 100644 index 0000000..b132046 --- /dev/null +++ b/src/minigame/interface/IMiniCommon.ts @@ -0,0 +1,53 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 小游戏一些通用方法 + */ + +export interface IMiniCommon { + /** + * 获取冷启动参数 + */ + getLaunchOptions(): Record; + /** + * 获取热启动参数 + */ + getHotLaunchOptions(): Record; + + /** + * 获取基础库版本号 + */ + getLibVersion(): string; + + /** + * 获取运行平台 合法值(ios | android | ohos | windows | mac | devtools | iPad) + * 微信上 iPad 会返回 ios + */ + getPlatform(): 'ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools' | 'iPad'; + + /** + * 获取运行类型 + * 合法值(release | debug) + */ + getEnvType(): 'release' | 'debug'; + + /** + * 宿主程序版本 (这里指微信版本) + */ + getHostVersion(): string; + + /** + * 获取屏幕尺寸 + */ + getScreenSize(): { width: number, height: number }; + + /** + * 退出小程序 + */ + exitMiniProgram(): void; + + /** + * 复制到剪切板 + */ + setClipboardData(text: string): void; +} \ No newline at end of file diff --git a/src/minigame/interface/IMiniPay.ts b/src/minigame/interface/IMiniPay.ts new file mode 100644 index 0000000..8f7a944 --- /dev/null +++ b/src/minigame/interface/IMiniPay.ts @@ -0,0 +1,69 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 小游戏支付接口 + */ + +export interface IMiniPayParams { + /** + * 支付金额 (元) + */ + rmb: number; + /** + * 订单号 + */ + orderId: string; + /** + * 是否为沙盒环境 0: 正式环境 1: 沙盒环境 + */ + sandbox?: 0 | 1; + /** + * 商品ID + */ + shopId: string; + + /** + * 商品名 + */ + shopName: string; + + /** + * 额外信息 + */ + extraInfo?: Record; + + /** + * 接口调用成功的回调函数 + * @param res.code 支付结果码 + * @param res.message 支付结果信息 + */ + success: (res: { code: number, message: string }) => void; + + /** + * 接口调用失败的回调函数 + * @param res.errCode 错误码 + * @param res.errMsg 错误信息 + */ + fail: (res: { errCode: number, errMsg: string }) => void; +} + +export interface IMiniPay { + /** + * 初始化 (不需要的参数传null) + * @param offerId 商户号 + * @param unitPriceQuantity 单价数量 1元 / 后台设置的价格单位 + */ + init(offerId: string, unitPriceQuantity: number): void; + + /** + * 是否满足限定的价格等级 + * @param rmb 价格 (元) + * @returns 是否满足限定的价格等级 + */ + isPayable(rmb: number): boolean; + + /** + * 支付 + */ + pay(res: IMiniPayParams): void; +} \ No newline at end of file diff --git a/src/minigame/wechat/WechatAds.ts b/src/minigame/wechat/WechatAds.ts new file mode 100644 index 0000000..b3e34f9 --- /dev/null +++ b/src/minigame/wechat/WechatAds.ts @@ -0,0 +1,78 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 微信广告 + */ + +import { warn } from "../../kunpocc"; +import { MiniErrorCode } from "../header"; +import { IMiniRewardAds } from "../interface/IMiniAds"; + +export class WechatAds implements IMiniRewardAds { + private _adUnitId: string = ""; + private _video_ad: WechatMiniprogram.RewardedVideoAd = null; + + /** + * 广告成功回调 + */ + private _success: () => void; + /** + * 广告失败回调 + */ + private _fail: (errCode: number, errMsg: string) => void; + + public init(adUnitId: string): void { + this._adUnitId = adUnitId; + } + + /** + * 显示广告 + */ + public showAds(res: { success: () => void, fail: (errCode: number, errMsg: string) => void }): void { + if (this._adUnitId === "") { + warn(MiniErrorCode.AD_NOT_INIT.msg); + res.fail(MiniErrorCode.AD_NOT_INIT.code, MiniErrorCode.AD_NOT_INIT.msg); + return; + } + this._success = res.success; + this._fail = res.fail; + + if (!this._video_ad) { + this._video_ad = this.createVideoAd(); + } + this._video_ad.load().then(() => { + this._video_ad.show(); + }).catch((res: { errMsg: string; errNo: number }) => { + warn(`广告加载失败 errCode:${res.errNo} errMsg:${res.errMsg}`); + this._fail(res.errNo, res.errMsg); + this.reset(); + }); + } + + private createVideoAd(): WechatMiniprogram.RewardedVideoAd { + let videoAd = wx.createRewardedVideoAd({ adUnitId: this._adUnitId }); + /** 激励视频错误事件的监听函数 */ + videoAd.onError((res: WechatMiniprogram.RewardedVideoAdOnErrorListenerResult) => { + warn(`激励视频广告 onError:${res.errCode}:${res.errMsg}`); + this._fail(res.errCode, res.errMsg); + this.reset(); + }); + videoAd.onClose((res: WechatMiniprogram.RewardedVideoAdOnCloseListenerResult) => { + if ((res && res.isEnded) || res === undefined) { + /** 广告播放完成 */ + this?._success(); + } else { + /** 中途退出,不发放奖励 */ + this?._fail(MiniErrorCode.AD_EXIT.code, MiniErrorCode.AD_EXIT.msg); + } + this.reset(); + }); + return videoAd; + } + + /** 防止多次回调 */ + private reset(): void { + this._success = null; + this._fail = null; + } +} \ No newline at end of file diff --git a/src/minigame/wechat/WechatCommon.ts b/src/minigame/wechat/WechatCommon.ts new file mode 100644 index 0000000..eb8fa7d --- /dev/null +++ b/src/minigame/wechat/WechatCommon.ts @@ -0,0 +1,208 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 微信小游戏工具类 + */ + +import { warn } from "../../tool/log"; +import { IMiniCommon } from "../interface/IMiniCommon"; + +export class WechatCommon implements IMiniCommon { + private _launchOptions: WechatMiniprogram.LaunchOptionsApp = null; + private _accountInfo: WechatMiniprogram.AccountInfo = null; + + /** 基础库 2.25.3 开始支持的信息 */ + private _appBaseInfo: WechatMiniprogram.AppBaseInfo = null; + private _deviceInfo: WechatMiniprogram.DeviceInfo = null; + private _windowInfo: WechatMiniprogram.WindowInfo = null; + + /** 从基础库 2.20.1 开始,本接口停止维护 */ + private _systemInfo: WechatMiniprogram.SystemInfo = null; + + /** + * @internal + */ + constructor() { + // 获取冷启动参数 + this._launchOptions = wx.getLaunchOptionsSync(); + } + + /** + * 获取冷启动参数 + */ + public getLaunchOptions(): WechatMiniprogram.LaunchOptionsApp { + return this._launchOptions; + } + + /** + * 获取热启动参数 + */ + public getHotLaunchOptions(): WechatMiniprogram.LaunchOptionsApp { + return wx.getEnterOptionsSync(); + } + + /** + * 获取基础库版本号 + */ + public getLibVersion(): string { + return this.getAppBaseInfo()?.SDKVersion || "0.0.1"; + } + + /** + * 宿主程序版本 (这里指微信版本) + */ + public getHostVersion(): string { + return this.getAppBaseInfo()?.version || "0.0.1"; + } + + /** + * 获取运行平台 + */ + public getPlatform(): 'ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools' { + return this.getDeviceInfo().platform as ('ios' | 'android' | 'ohos' | 'windows' | 'mac' | 'devtools'); + } + + /** + * 获取版本类型 + */ + public getEnvType(): 'release' | 'debug' { + return this.getVersionInfo().miniProgram.envVersion == "release" ? "release" : "debug"; + } + + /** + * 退出小程序 + */ + public exitMiniProgram(): void { + wx.exitMiniProgram?.(); + } + + public getScreenSize(): { width: number, height: number } { + const windowInfo = this.getWindowInfo(); + return { + width: windowInfo.screenWidth, + height: windowInfo.screenHeight, + }; + } + + /** + * 复制到剪切板 + */ + public setClipboardData(text: string): void { + wx.setClipboardData({ + data: text, + fail: (res: WechatMiniprogram.GeneralCallbackResult) => { + warn("复制到剪切板失败", res.errMsg); + } + }); + } + + private getAppBaseInfo(): WechatMiniprogram.AppBaseInfo { + if (this._appBaseInfo) { + return this._appBaseInfo; + } + if (wx.getAppBaseInfo) { + this._appBaseInfo = wx.getAppBaseInfo(); + return this._appBaseInfo; + } + const systemInfo = this.getSystemInfo(); + if (systemInfo) { + this._appBaseInfo = { + SDKVersion: systemInfo.SDKVersion, + enableDebug: systemInfo.enableDebug, + host: systemInfo.host, + language: systemInfo.language, + version: systemInfo.version, + theme: systemInfo.theme, + } + return this._appBaseInfo; + } + warn("getAppBaseInfo 失败"); + return null; + } + + private getVersionInfo(): WechatMiniprogram.AccountInfo { + if (this._accountInfo) { + return this._accountInfo; + } + if (wx.getAccountInfoSync) { + this._accountInfo = wx.getAccountInfoSync(); + return this._accountInfo; + } + warn("getVersionInfo 失败"); + return { + miniProgram: { + envVersion: "release", + appId: "unknown", + version: "0.0.1", + }, + plugin: { + appId: "unknown", + version: "0.0.1", + }, + }; + } + + public getDeviceInfo(): WechatMiniprogram.DeviceInfo { + if (this._deviceInfo) { + return this._deviceInfo; + } + if (wx.getDeviceInfo) { + this._deviceInfo = wx.getDeviceInfo(); + return this._deviceInfo; + } + const systemInfo = this.getSystemInfo(); + if (systemInfo) { + this._deviceInfo = { + abi: "unknown", + benchmarkLevel: systemInfo.benchmarkLevel, + brand: systemInfo.brand, + cpuType: "unknown", + deviceAbi: "unknown", + memorySize: "unknown", + model: systemInfo.model, + platform: systemInfo.platform, + system: systemInfo.system, + } + return this._deviceInfo; + } + warn("getDeviceInfo 失败"); + return null; + } + + public getWindowInfo(): WechatMiniprogram.WindowInfo { + if (this._windowInfo) { + return this._windowInfo; + } + if (wx.getWindowInfo) { + this._windowInfo = wx.getWindowInfo(); + return this._windowInfo; + } + const systemInfo = this.getSystemInfo(); + if (systemInfo) { + this._windowInfo = { + pixelRatio: systemInfo.pixelRatio, + safeArea: systemInfo.safeArea, + screenHeight: systemInfo.screenHeight, + screenTop: 0, + screenWidth: systemInfo.screenWidth, + statusBarHeight: systemInfo.statusBarHeight, + windowHeight: systemInfo.windowHeight, + windowWidth: systemInfo.windowWidth, + } + } + warn("getWindowInfo 失败"); + return null; + } + + private getSystemInfo(): WechatMiniprogram.SystemInfo { + if (this._systemInfo) { + return this._systemInfo; + } + if (wx.getSystemInfoSync) { + this._systemInfo = wx.getSystemInfoSync(); + return this._systemInfo; + } + warn("getSystemInfo 失败"); + return null; + } +} \ No newline at end of file diff --git a/src/minigame/wechat/WechatPay.ts b/src/minigame/wechat/WechatPay.ts new file mode 100644 index 0000000..058d906 --- /dev/null +++ b/src/minigame/wechat/WechatPay.ts @@ -0,0 +1,67 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: 微信支付 + */ + +import { log, warn } from "../../tool/log"; +import { MiniErrorCode, PriceLimitList } from "../header"; +import { IMiniPay, IMiniPayParams } from "../interface/IMiniPay"; +import { MiniHelper } from "../MiniHelper"; + +export class WechatPay implements IMiniPay { + private _offerId: string = ""; + private _unitPriceQuantity: number = 1; + public init(offerId: string, unitPriceQuantity: number): void { + this._offerId = offerId; + this._unitPriceQuantity = unitPriceQuantity; + } + + public isPayable(rmb: number): boolean { + return PriceLimitList.includes(rmb); + } + + public pay(res: IMiniPayParams): void { + if (this._offerId === "") { + warn("请先调用 init 方法初始化"); + res.fail({ errCode: MiniErrorCode.PAY_NOT_INIT.code, errMsg: MiniErrorCode.PAY_NOT_INIT.msg }); + return; + } + if (!this.isPayable(res.rmb)) { + res.fail({ errCode: -15016, errMsg: "传入价格不满足限定条件" }); + return; + } + let platform = MiniHelper.common().getPlatform(); + if (platform === "ios") { + res.fail({ errCode: MiniErrorCode.IOS_FORBIDDEN.code, errMsg: MiniErrorCode.IOS_FORBIDDEN.msg }); + return; + } + + if (platform === "windows" || platform === "mac") { + platform = "windows"; + } else { + platform = "android"; + } + log(`WechatPay rmb:${res.rmb}元 orderId:${res.orderId} sandbox:${res.sandbox || 0} shopId:${res.shopId} shopName:${res.shopName}`); + + wx.requestMidasPayment({ + mode: "game", + /** 沙箱环境 */ + env: res.sandbox || 0, + offerId: this._offerId, + currencyType: "CNY", + platform: platform, + buyQuantity: res.rmb * this._unitPriceQuantity, + zoneId: "1", + /** 游戏唯一订单号 */ + outTradeNo: res.orderId, + success: (param: { errMsg: string }) => { + res.success({ code: 0, message: param.errMsg }); + }, + fail: (param: { errCode: number, errMsg: string, errno: number }) => { + warn(`WechatPay fail code:${param.errCode} msg:${param.errMsg} errno:${param.errno}`); + res.fail({ errCode: param.errCode, errMsg: param.errMsg }); + } + }); + } +} \ No newline at end of file diff --git a/src/net/socket/Socket.ts b/src/net/socket/Socket.ts index 23ea289..c873ab9 100644 --- a/src/net/socket/Socket.ts +++ b/src/net/socket/Socket.ts @@ -4,7 +4,6 @@ * @Description: 网络socket */ -import { SocketTask } from "@douyin-microapp/typings/types/api"; import { Platform } from "../../global/Platform"; import { debug, warn } from "../../tool/log"; @@ -36,7 +35,7 @@ export class Socket { * socket对象 * @internal */ - private _socket: WebSocket | WechatMiniprogram.SocketTask | AliyMiniprogram.SocketTask | SocketTask; + private _socket: WebSocket | WechatMiniprogram.SocketTask | AliyMiniprogram.SocketTask | BytedanceMiniprogram.SocketTask; /** * @param {string} url 要连接的 URL;这应该是 WebSocket 服务器将响应的 URL @@ -129,8 +128,8 @@ export class Socket { * 抖音小游戏创建socket * @internal */ - private createBytedanceSocket(url: string, timeout?: number, protocols?: string[]): SocketTask { - let socket: SocketTask = tt.connectSocket({ + private createBytedanceSocket(url: string, timeout?: number, protocols?: string[]): BytedanceMiniprogram.SocketTask { + let socket: BytedanceMiniprogram.SocketTask = tt.connectSocket({ url, protocols: protocols, success: () => { debug("socket success") }, @@ -208,7 +207,7 @@ export class Socket { } else if (Platform.isAlipay) { (this._socket as AliyMiniprogram.SocketTask).send({ data: data }); } else if (Platform.isBytedance) { - (this._socket as SocketTask).send({ data: data }); + (this._socket as BytedanceMiniprogram.SocketTask).send({ data: data }); } else { (this._socket as WebSocket).send(data); } @@ -230,7 +229,7 @@ export class Socket { (this._socket as AliyMiniprogram.SocketTask).send({ data: data }); } } else if (Platform.isBytedance) { - (this._socket as SocketTask).send({ data: data }); + (this._socket as BytedanceMiniprogram.SocketTask).send({ data: data }); } else { (this._socket as WebSocket).send(data); } @@ -247,7 +246,7 @@ export class Socket { } else if (Platform.isAlipay) { (this._socket as AliyMiniprogram.SocketTask).close({ code: code, reason: reason }); } else if (Platform.isBytedance) { - (this._socket as SocketTask).close({ code: code, reason: reason }); + (this._socket as BytedanceMiniprogram.SocketTask).close({ code: code, reason: reason }); } else { (this._socket as WebSocket).close(code, reason); } diff --git a/src/tool/Utils.ts b/src/tool/Utils.ts new file mode 100644 index 0000000..a037231 --- /dev/null +++ b/src/tool/Utils.ts @@ -0,0 +1,39 @@ +/** + * @Author: Gongxh + * @Date: 2025-04-11 + * @Description: + */ + +export class Utils { + /** + * 版本号比较 + * @param version1 本地版本号 + * @param version2 远程版本号 + * 如果返回值大于0,则version1大于version2 + * 如果返回值等于0,则version1等于version2 + * 如果返回值小于0,则version1小于version2 + */ + public static compareVersion(version1: string, version2: string): number { + let v1 = version1.split('.'); + let v2 = version2.split('.'); + const len = Math.max(v1.length, v2.length); + while (v1.length < len) { + v1.push('0'); + } + while (v2.length < len) { + v2.push('0'); + } + + for (let i = 0; i < len; ++i) { + let num1 = parseInt(v1[i]); + let num2 = parseInt(v2[i]); + if (num1 > num2) { + return 1; + } else if (num1 < num2) { + return -1; + } + } + return 0; + } + +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0972cee..811000a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "@cocos/creator-types/editor", "./libs/lib.wx.api.d.ts", "./libs/lib.ali.api.d.ts", - "@douyin-microapp/typings" + "./libs/lib.bytedance.api.d.ts", + // "@douyin-microapp/typings" ] }, "include": [