小游戏广告,支付和一些常用接口处理

This commit is contained in:
宫欣海
2025-04-12 19:07:21 +08:00
parent e6827ae014
commit de804f7fc7
22 changed files with 2134 additions and 37 deletions

View File

@@ -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;

View File

@@ -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";

View File

@@ -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 extends IMiniCommon>(): 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 extends IMiniRewardAds>(): 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 extends IMiniPay>(): 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<string, any> {
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;
}
}

View File

@@ -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 });
}
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<string, any> = {
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<BytedanceCommon>().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 });
}
});
}
}

24
src/minigame/header.ts Normal file
View File

@@ -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];

View File

@@ -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;
}

View File

@@ -0,0 +1,53 @@
/**
* @Author: Gongxh
* @Date: 2025-04-11
* @Description: 小游戏一些通用方法
*/
export interface IMiniCommon {
/**
* 获取冷启动参数
*/
getLaunchOptions(): Record<string, any>;
/**
* 获取热启动参数
*/
getHotLaunchOptions(): Record<string, any>;
/**
* 获取基础库版本号
*/
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;
}

View File

@@ -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<string, any>;
/**
* 接口调用成功的回调函数
* @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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 });
}
});
}
}

View File

@@ -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);
}

39
src/tool/Utils.ts Normal file
View File

@@ -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;
}
}