392 lines
14 KiB
TypeScript
392 lines
14 KiB
TypeScript
import cloud from '@lafjs/cloud'
|
||
import { createHash } from 'crypto';
|
||
const db = cloud.database();
|
||
import crypto from 'crypto';
|
||
const mchid = "1719895437";//商户id
|
||
const serialNo = "2A97ED6ED441E55600EBC8830B9EC889ED41496E";//证书序列号
|
||
const ThinkingData = require('thinkingdata-node');
|
||
import Ip2region from 'ip2region';
|
||
const query = new Ip2region();
|
||
const canIos = false;
|
||
const version = "1.9.91";
|
||
export default async function (ctx: FunctionContext) {
|
||
let code = ctx.body.code;
|
||
let distinctId = ctx.body.distinctId;
|
||
let isDebug = ctx.body.isDebug;
|
||
let dbname = "users";
|
||
const gameName = ctx.body.gameName;
|
||
if (gameName == "iaa") {
|
||
dbname = "usersAd";
|
||
}
|
||
let idname = "687a0e79c03536d5ff982f77";
|
||
if (gameName == "iaa") {
|
||
idname = "68e90168b30b6944c56bdb74";
|
||
}
|
||
let info = await getWXOpenId(code, dbname);
|
||
if (!info) {
|
||
return;
|
||
}
|
||
let openid = info[0];
|
||
let session_key = info[1];
|
||
let unionid = info[2];
|
||
let userData = await getUserData(dbname, openid);
|
||
let token = await stringToHash(Date.now() + openid);
|
||
if (!userData) {
|
||
let ref = await db.collection("idcount").field({ count: 1 }).where({ _id: idname }).getOne();
|
||
await db.collection("idcount").where({ _id: idname }).update({ count: ref.data.count + 1 });
|
||
await db.collection(dbname).add({
|
||
openid: openid,
|
||
unionid: unionid,
|
||
session_key: session_key,
|
||
register_time: Date.now(),
|
||
onlyId: ref.data.count,
|
||
distinctId: distinctId,
|
||
isDebug: isDebug,
|
||
levelAmount: 0,
|
||
coinAmount: 0,
|
||
healthAmount: 5,
|
||
healthtimestamp: 0,
|
||
freezeAmount: 3,
|
||
hammerAmount: 3,
|
||
magicAmount: 3,
|
||
userPowerTime: 0,
|
||
monthCardReward: 0,
|
||
monthCardTime: 0,
|
||
rebornGiftCount: 0,
|
||
rebornGiftTime: 0,
|
||
useravatar: Math.floor(Math.random() * 4) + "",
|
||
username: "user",
|
||
pay_user: false,
|
||
isWhite: false,
|
||
isFirst: true,
|
||
starter_pack: 0,
|
||
starter_packState: 0,
|
||
careerAmount: 0,
|
||
useravatarIcon: "0",
|
||
forcedUpdate: false,
|
||
token: token
|
||
});
|
||
} else {
|
||
let taskTime = userData.taskTime || 0;
|
||
let task = userData.task;
|
||
if (task) {
|
||
task = JSON.parse(task);
|
||
} else {
|
||
task = {
|
||
levelPass: {
|
||
value: 0,
|
||
state: 0,
|
||
},
|
||
share: {
|
||
value: 0,
|
||
state: 0,
|
||
},
|
||
useEnergy: {
|
||
value: 0,
|
||
state: 0,
|
||
},
|
||
useProp: {
|
||
value: 0,
|
||
state: 0,
|
||
}
|
||
}
|
||
}
|
||
const now = new Date(Date.now() + 8 * 3600000);
|
||
now.setHours(0, 0, 0, 0); // 直接修改当前Date对象
|
||
const timestamp = now.getTime();
|
||
if (timestamp > taskTime) {
|
||
for (var key in task) {
|
||
task[key].value = 0;
|
||
task[key].state = 0;
|
||
}
|
||
}
|
||
await db.collection(dbname).where({ _id: userData._id }).update({ session_key: session_key, distinctId: distinctId, isDebug: isDebug, isFirst: false, unionid: unionid, task: JSON.stringify(task), taskTime: Date.now() + 8 * 3600000, token: token });
|
||
}
|
||
if (userData?.freeze) {
|
||
return { code: 0, data: "账号封禁", msg: "账号封禁" };
|
||
}
|
||
await delayExecution(0.1);
|
||
userData = await getUserData(dbname, openid);
|
||
let res = null;
|
||
if (userData.openid) {
|
||
res = await db.collection("order").where({ state: 1, openid: userData.openid }).get();
|
||
}
|
||
let outTradeNo: any = [];
|
||
if (res.data && res.data.length > 0) {
|
||
for (let i = 0; i < res.data.length; i++) {
|
||
outTradeNo.push(res.data[i]);
|
||
}
|
||
}
|
||
if (dbname == "users") {
|
||
let ret = await getIosOrder(userData.openid, ctx);
|
||
outTradeNo = outTradeNo.concat(ret);
|
||
userData.outTradeNo = outTradeNo;
|
||
}
|
||
|
||
if (!userData.onlyId) {
|
||
let ref = await db.collection("idcount").field({ count: 1 }).where({ _id: idname }).getOne();
|
||
await db.collection("idcount").where({ _id: idname }).update({ count: ref.data.count + 1 });
|
||
userData.onlyId = ref.data.count;
|
||
await db.collection(dbname).where({ _id: userData._id }).update({ session_key: session_key, onlyId: ref.data.count });
|
||
}
|
||
let share = userData.shareLv;
|
||
if (share) {
|
||
userData.shareLv = JSON.parse(share);
|
||
} else {
|
||
userData.shareLv = [];
|
||
}
|
||
userData.careerAmount = userData.careerAmount || 0;
|
||
let rebornGiftTime = userData?.rebornGiftTime || 0;
|
||
let times = getTodayStartTimestamp();
|
||
if (rebornGiftTime < times) {
|
||
userData.rebornGiftCount = 0;
|
||
}
|
||
if (userData.isWhite && !userData.film && userData.film != 0) {
|
||
userData.film = userData.levelAmount;
|
||
}
|
||
userData.canIos = canIos;
|
||
userData.version = version;
|
||
//console.log(userData)
|
||
//console.log("查询时间:");
|
||
return { code: 1, data: userData }
|
||
}
|
||
function stringToHash(input: string, algorithm: 'sha1' | 'sha256' | 'sha512' = 'sha256'): string {
|
||
return createHash(algorithm).update(input).digest('hex');
|
||
}
|
||
function getTodayUTCMidnightTimestamp(): number {
|
||
const now = new Date();
|
||
// 1. 获取当前 UTC 时间
|
||
const utcMidnight = Date.UTC(
|
||
now.getUTCFullYear(),
|
||
now.getUTCMonth(),
|
||
now.getUTCDate(),
|
||
0, 0, 0, 0
|
||
);
|
||
// 2. 加上 8 小时(北京时间 = UTC+8)
|
||
return utcMidnight + 8 * 60 * 60 * 1000;
|
||
}
|
||
const getClientIp = (req) => {
|
||
return (
|
||
req.headers['x-forwarded-for']?.split(',')[0]?.trim() || // 代理链中的第一个 IP
|
||
req.headers['x-real-ip'] || // Nginx 配置的 real-ip
|
||
req.socket.remoteAddress // 原始 IP(可能是代理)
|
||
);
|
||
};
|
||
|
||
const propName = {
|
||
"金币包1": "gold_1",
|
||
"金币包2": "gold_2",
|
||
"金币包3": "gold_3",
|
||
"金币包4": "gold_4",
|
||
"金币包5": "gold_5",
|
||
"金币包6": "gold_6",
|
||
"无限体力组合包1": "unlimited_health_bundle_1",
|
||
"无限体力组合包2": "unlimited_health_bundle_2",
|
||
"无限体力组合包3": "unlimited_health_bundle_3",
|
||
"局内购买冻结": "freeze_in_game",
|
||
"局内购买锤子": "hammer_in_game",
|
||
"局内购买魔棒": "wand_in_gamerefill",
|
||
"补满体力": "health"
|
||
};
|
||
|
||
function getTodayStartTimestamp() {
|
||
const date = new Date();
|
||
date.setHours(0, 0, 0, 0);
|
||
return date.getTime();
|
||
}
|
||
async function getWXOpenId(code, dbname) {
|
||
let appid = "wxdd145ced49158a1e";
|
||
let appsecret = "7f295245ffc4884b03306c64e30654db";
|
||
if (dbname == "usersAd") {
|
||
appid = "wxe69a671ea2c93af3";
|
||
appsecret = "1eef42cd8810024eaea6dac961f9b3b0";
|
||
}
|
||
const api_url = `https://api.weixin.qq.com/sns/jscode2session`;
|
||
const param = `appid=${appid}&secret=${appsecret}&js_code=${code}&grant_type=authorization_code`;
|
||
let res = null;
|
||
try {
|
||
res = await cloud.fetch(`${api_url}?${param}`, {
|
||
headers: {
|
||
"Accept-Encoding": 'gzip'
|
||
}
|
||
});
|
||
} catch {
|
||
|
||
}
|
||
|
||
|
||
if (res == null || res.data.errcode > 0) {
|
||
return null
|
||
}
|
||
//console.log(res.data);
|
||
return [res?.data?.openid, res?.data?.session_key, res?.data?.unionid]
|
||
}
|
||
/**获取当前用户数据 */
|
||
async function getUserData(dbname: string, openid: any, isFirst?) {
|
||
let { data } = await db.collection(dbname).where({ openid: openid }).get();
|
||
if (data.length) {
|
||
return data[0];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function delayExecution(seconds: number) {
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
// 在这里编写需要延时执行的代码
|
||
resolve("延时执行完成");
|
||
}, seconds * 1000);
|
||
});
|
||
}
|
||
async function getIosOrder(openid, ctx) {
|
||
let res = await db.collection("order").where({ state: 0, type: "ios", openid: openid }).get();
|
||
let res1 = await db.collection("users").where({ openid: openid }).getOne();
|
||
let list = [];
|
||
if (res.data.length > 0) {
|
||
for (let i = 0; i < res.data.length; i++) {
|
||
let ret = await getOrder(res.data[i].outTradeNo);
|
||
if (ret.trade_state == "SUCCESS") {
|
||
list.push(res.data[i]);
|
||
sead(res.data[i], res1.data, getClientIp(ctx));
|
||
switch (res.data[i].itemid) {
|
||
case "month_Card":
|
||
const latestUser = await db.collection("users").where({ _id: res1.data._id }).getOne();
|
||
const now = Date.now();
|
||
const oldMonthCardTime = Number(latestUser.data?.monthCardTime) || 0;
|
||
const monthCardTime = Math.max(oldMonthCardTime, now) + 2592000000;
|
||
const monthCardReward = oldMonthCardTime > now ? (Number(latestUser.data?.monthCardReward) || 0) : 0;
|
||
await db.collection("users").where({ _id: res1.data._id }).update({ monthCardTime: monthCardTime, monthCardReward: monthCardReward, pay_user: true });
|
||
await db.collection("order").where({ outTradeNo: res.data[i].outTradeNo }).update({ monthCardGranted: true });
|
||
break;
|
||
case "reborn_Gift":
|
||
db.collection("users").where({ _id: res1.data._id }).update({ rebornGiftTime: Date.now(), rebornGiftCount: 1, pay_user: true });
|
||
break;
|
||
case "starter_pack":
|
||
db.collection("users").where({ _id: res1.data._id }).update({ starter_packState: 1, pay_user: true });
|
||
break;
|
||
default:
|
||
db.collection("users").where({ _id: res1.data._id }).update({ pay_user: true });
|
||
break;
|
||
}
|
||
// 月卡补单确认成功后进入待客户端补发状态,避免下次登录重复累计。
|
||
if (res.data[i].itemid === "month_Card") {
|
||
await db.collection("order").where({ outTradeNo: res.data[i].outTradeNo }).update({ state: 1 });
|
||
}
|
||
} else {
|
||
if (Date.now() - res.data[i].time > 3 * 3600000) {
|
||
let ret = await db.collection('order').where({ outTradeNo: res.data[i].outTradeNo }).remove({ multi: true });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
async function sead(order, userInfo, ip) {
|
||
if (!userInfo.distinctId) {
|
||
return;
|
||
}
|
||
let id = '87d18958cea145f29d3265470ecd3486';
|
||
if (userInfo.isDebug != "true") {
|
||
id = '121591378fc1423893deb12041413eb3';
|
||
}
|
||
let teSDK = ThinkingData.initWithBatchMode(id, 'https://data.nika4fun.com/sync_data', {
|
||
dryRun: false, // report data to TE or not
|
||
deviceId: "123456789",//设备唯一标识
|
||
});
|
||
let trackEvent = {
|
||
accountId: order.openid,
|
||
distinctId: userInfo.distinctId,//访客id
|
||
event: 'payment',
|
||
time: new Date(Date.now() + 8 * 3600000),
|
||
ip: ip,
|
||
properties: {
|
||
order_id: order.outTradeNo,
|
||
pay_amount: order.goodsPrice,
|
||
payment_name: order.itemid,
|
||
payment_num: order.itemCount,
|
||
payment_type: "ios",
|
||
current_level: userInfo.lv, //当前关卡等级 number
|
||
current_health: userInfo.healthAmount, //当前体力值
|
||
tmp_coin: userInfo.coinAmount,//当前金币
|
||
version: "1.6",
|
||
user_id: userInfo._id, //用户id
|
||
pay_user: userInfo.pay_user
|
||
},
|
||
callback(e) {
|
||
if (e) {
|
||
console.log(e);
|
||
}
|
||
}
|
||
};
|
||
teSDK.track(trackEvent);
|
||
teSDK.close();
|
||
return;
|
||
}
|
||
/**获取订单*/
|
||
async function getOrder(outTradeNo) {
|
||
const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=1719895437`;
|
||
const nonceStr = crypto.randomBytes(16).toString('hex');
|
||
const timestamp = Math.floor(Date.now() / 1000).toString();
|
||
let au = await sign(outTradeNo, nonceStr, timestamp);
|
||
let res = await fetch(url, {
|
||
method: 'GET', // 默认是 GET,可省略
|
||
headers: {
|
||
'Authorization': au, // 认证令牌
|
||
'Content-Type': 'application/json', // 声明请求体类型(GET 请求通常不需要)
|
||
'Accept': 'application/json', // 声明期望的响应类型
|
||
}
|
||
})
|
||
const data = await res.json();
|
||
return data;
|
||
}
|
||
async function sign(outTradeNo, nonceStr, timestamp) {
|
||
const data = `GET\n/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=1719895437\n${timestamp}\n${nonceStr}\n\n`
|
||
// 创建签名对象
|
||
const sign = crypto.createSign('RSA-SHA256');
|
||
// 更新签名内容
|
||
sign.update(data);
|
||
// 获取存储桶
|
||
const bucket = cloud.storage.bucket('j3k323ol-colorblock-oss');
|
||
// 获取文件列表
|
||
const res = await bucket.readFile('apiclient_key.pem');
|
||
const privateKeyPem = await res.Body.transformToString();
|
||
// 使用私钥签名(PKCS1 填充)
|
||
const signature = sign.sign({
|
||
key: privateKeyPem,
|
||
padding: crypto.constants.RSA_PKCS1_PADDING
|
||
}, 'base64');
|
||
// 构建Authorization头
|
||
const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timestamp}",serial_no="${serialNo}"`;
|
||
return authorization;
|
||
}
|
||
|
||
/**
|
||
* 根据 IP 获取省份名称
|
||
* @param {string} ip - 用户 IP 地址
|
||
* @returns {string} 省份名称(如 "广东省"),未知时返回 "未知"
|
||
*/
|
||
function getProvinceByIP(ip) {
|
||
try {
|
||
const result = query.search(ip);
|
||
//console.log(result);
|
||
if (!result || !result.country.includes('中国')) {
|
||
return '其他';
|
||
}
|
||
|
||
// ip2region 返回的格式示例:"中国|0|广东省|深圳市|电信"
|
||
const parts = result.province;
|
||
const provinceCode = parts[2]; // 省份代码在第三部分
|
||
|
||
// 检查是否是已知的省份代码
|
||
// if (PROVCE_CODE_MAP[provinceCode]) {
|
||
// return PROVINCE_CODE_MAP[provinceCode];
|
||
// }
|
||
|
||
// 如果未映射,返回原始代码(可能是数据库更新后的新代码)
|
||
return parts || '其他';
|
||
} catch (error) {
|
||
console.error('IP 解析失败:', error);
|
||
return '其他';
|
||
}
|
||
}
|