HJ_Server/src/public/pay.ts
2023-12-28 16:13:54 +08:00

572 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {ApiCall} from 'tsrpc';
import {YangChengMuBiaofun} from '../api_s2c/event/yangchengmubiao/fun';
import {MsgPayChange} from '../shared/protocols/msg_s2c/MsgPayChange';
import {payLog} from '../shared/protocols/pay/PtlGetList';
import {player} from '../shared/protocols/user/type';
import {PublicShared} from '../shared/public/public';
import {EmailFun} from './email';
import {HuoDongFun} from './huodongfun';
import {call, PlayerFun} from './player';
import {number} from "mathjs";
import {getGud} from './gud';
import {getConf as zmlbGetConf} from '../api_s2c/event/zhoumolibao/ApiOpen';
import {Christmasfun} from "../api_s2c/event/christmas/fun";
async function checkPayIsActive(payId: string, logs: payLog[], payArgs) {
let conf: any = await this.getConf(payId, payArgs);
if (!conf) return false;
let lastLog = logs.last();
if (!lastLog) return false;
if (conf.time == -1) return true;
return G.time < lastLog.eTime;
}
export class PayFun {
static async addPayLog(uid: string, payId: string, type: "system" | "user" | "legu", payArgs) {
let conf: any = await this.getConf(payId, payArgs);
let time = PublicShared.getToDayZeroTime(G.time);
let obj: payLog = {time: G.time, type: type};
if (conf.time != -1) obj.eTime = time + conf.time;
obj.args = payArgs
if (payId == 'G123SendGift') {
obj.popup_id = payArgs.popup_id
obj.template_id = payArgs.template_id
obj.buyNumber = conf.buyNumber
}
let result = await G.mongodb.collection('payLogNew').findOneAndUpdate(
{uid: uid, key: payId, del_time: {$exists: false}},
{$push: {values: obj}},
{upsert: true, returnDocument: 'after'}
);
let msg: MsgPayChange = {[payId]: result.value?.values || []};
if (payId == 'G123SendGift') {
G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', R.groupBy(i => i.popup_id)(result.value?.values));
} else {
G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', msg);
}
}
static changeG123GiftLog(uid: string, popupId: string) {
G.mongodb.collection('giftLog').updateOne({popup_id: popupId}, {$inc: {buyNumber: 1}}, {upsert: true});
}
static async delPayLog(uid: string, ...args: { payId: string, val: payLog[]; }[]) {
args.map(a => {
G.mongodb.collection('payLogNew').updateOne(
{uid: uid, key: a.payId, del_time: {$exists: false}},
{$set: {del_time: G.time}},
{upsert: true}
);
})
G.mongodb.collection('payLogNew').insertMany(args.map(i => ({key: i.payId, uid: uid, values: i.val})));
G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', Object.fromEntries(args.map(a => [a.payId, a.val])));
}
static async getPayLog(uid: string, payId: string): Promise<payLog[]>;
static async getPayLog(uid: string, payId: string) {
// let allPlayerPayLog = await G.redis.hGet('player:payLog', uid)
let val = await G.mongodb.collection("payLogNew").findOne({uid: uid, key: payId, del_time: {$exists: false}}, {
projection: {
_id: 0,
uid: 0
}
});
let allPlayerPayLog = {[payId]: val?.values || []}
return allPlayerPayLog?.[payId] || [];
}
static async getPayLogs(uid: string, payIds?: string[]) {
let where = {
uid: uid,
del_time: {$exists: false}
}
if (payIds?.length) where['key'] = {$in: payIds}
let logs = await G.mongodb.collection("payLogNew").find(where, {
projection: {
_id: 0,
uid: 0
}
}).toArray();
if (payIds?.length) {
return Object.fromEntries(payIds.map(id => [id, logs.find(i => i.key == id)?.values || []]));
}
let allLogs = {}
logs.map(i => allLogs[i.key] = i.values)
return allLogs
}
/**
* 获取支付配置主要是针对G123礼包、GM后台内部充值的特殊处理
* 让这两个不在预设充值挡位内的,可以正常发奖
* @param payId
* @param payArgs
*/
static async getConf(payId, payArgs) {
let conf: any = G.gc.pay[payId]
if (payId == 'G123SendGift') {
let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});
let gPrize = giftInfo.items
let bPrize = []
for (let index in gPrize) {
let args = index.split('^')
bPrize.push({a: args[0], t: args[1], n: gPrize[index]})
}
conf = {
id: 'G123SendGift',
money: giftInfo.price,
payExp: [{"a": "attr", "t": "payExp", "n": giftInfo.vipPoints}],
prize: bPrize,
firstPayPrize: [],
name: 'G123SendGift',
time: -1,
buys: giftInfo.purchaseLimitAmount,
needVip: 0,
front: {},
buyNumber: giftInfo.buyNumber
}
} else if (payId == 'LeguSendGift') {
conf = {
id: 'LeguSendGift',
money: payArgs.money,
payExp: payArgs.payExp,
prize: payArgs.prize,
firstPayPrize: [],
name: 'LeguSendGift',
time: -1,
buys: 0,
needVip: 0,
front: {}
}
} else if (payId == '136GiftAll') {
// 136礼包一键购买加入所有礼包奖励
conf = {
...conf,
prize: R.compose(R.flatten(), R.map(i => i.prize), R.filter(i => i.id.indexOf('136Gift') != -1), R.values())(G.gc.pay)
}
}
return conf
}
/**
* 实际作用是支付后发奖
* 针对G123礼包有特殊处理不在游戏配置内预设的充值挡位
* todo 目前还缺验证通道,内部充值可以直接调用
* @param uid
* @param payId
* @param payArgs
* @param type
* @param orderNo
*/
static async pay(uid: string, payId: string, payArgs?: any, type?: "system" | "user" | "legu", orderNo: string = '') {
if (orderNo) {
//判断订单号是否已经支付过
let payed = await G.mongodb.collection('payOrderLog' as any).count({"orderNo": orderNo});
if (payed) {
console.log(uid, payId, payArgs, orderNo);
console.log('订单号已存在');
return;
}
}
/**
* 不要轻易改动这个方法,不允许集成业务逻辑在内部
* 如果有业务逻辑通过事件驱动在支付成功后emit事件
*/
let conf: any = await this.getConf(payId, payArgs);
let prize = [...conf.prize];
let player = await getGud(uid);
if (conf.firstPayPrize.length > 0) {
let logs = await this.getPayLog(uid, payId);
if (!logs || logs.length < 1) {
prize = PublicShared.mergePrize([].concat(conf.prize, conf.firstPayPrize));
}
}
let call = this.getCall(player);
call.req = {
uid: uid,
payId: payId,
payArgs: payArgs,
orderNo: orderNo
}
//改为统一方法,此处自选不这样用
// if (payId.indexOf('zixuanlibao') != -1) {
// // 后端唯一标识 htype 4 自选/定制 礼包
// // @ts-ignore
// let _hdList = await HuoDongFun.gethdList(call, 4)
// _hdList.forEach(ele => {
// let selectPrize = ele.data.gift.find(v => v.payId == payId)?.prize;
// if (prize) {
// let index = payArgs instanceof Array ? payArgs : [];
// let select = selectPrize.map((v, i) => v[index[i]] ? v[index[i]] : v[0]);
// prize.push(...select);
// }
// })
// }
if (payId.indexOf('ycmb') != -1) {
// @ts-ignore // 养成活动充值礼包
let _hdids = await YangChengMuBiaofun.getCon(call);
Object.values(_hdids).forEach(ele => {
let selectPrize = ele.data.gift.find(v => v.payId == payId)?.prize;
if (selectPrize) {
prize.push(...selectPrize);
}
});
}
if (payId.indexOf('wkdlibao') != -1) {
let call = this.getCall(player)
let conf = await zmlbGetConf(call, {payId})
prize.push(...conf.prize)
}
/**
* todo 不要轻易改动这个方法,不允许集成业务逻辑在内部
* todo 此方法仅仅是支付成功后的发奖
* todo 如果有业务逻辑通过事件驱动在支付成功后emit事件
* todo 下面这个方法不应该这样写
*/
//圣诞节活动充值
try {
await Christmasfun.payChristmas(payId, call);
} catch (e) {
console.log("Christmasfun.payChristmas Error", e);
}
/**
* 如果是自选奖励payArgs里传入自选项直接发奖
* 在check里判断拦截自选项的奖励
*/
if (payArgs && payArgs?.htype && payArgs?.selectList) {
let selectPrize = await G.ioredis.get(`pay:${payId}:${player.uid}`);
if (selectPrize) {
prize.push(...JSON.parse(selectPrize))
G.ioredis.del(`pay:${payId}:${player.uid}`)
}
}
let isReplaceConf = await this.checkBuysAfterPay(uid, payId, conf, payArgs, player)
if (isReplaceConf) {
let prizePayId = `zuanshi_${conf.money}`
payArgs.toPrizePayId = prizePayId
conf = this.replacePrizeToChongzhi(prizePayId, conf)
prize = conf.prize;
}
await PlayerFun.sendPrize(call, prize);
await PlayerFun.addAttr(call, conf.payExp);
if (payId == 'G123SendGift') {
this.changeG123GiftLog(uid, payArgs.popup_id)
}
await this.addPayLog(player.uid, payId, type, payArgs);
// this.addPayLogNew(player.uid, payId, type);
//记录支付信息log
if (orderNo) {
await G.mongodb.collection('payOrderLog' as any).insertOne({
"orderNo": orderNo,
"payId": payId,
"uid": uid,
"payArgs": payArgs,
"type": type,
"ctime": new Date().getTime()
});
}
G.server.sendMsgByUid(uid, 'msg_s2c/Collection', {
fromApi: `pay_${payId}`,
msg: call.eventMsg
});
G.server.sendMsgByUid(uid, 'msg_s2c/PayResult', {
code: 1,
data: {
id: payId,
prize: prize,
price: payArgs?.price
}
});
G.emit('PLAYER_PAY', player, payId, payArgs, call);
G.emit("Class_task_116", 'Class_task_116', call, 1, 0);
}
/**
* 转换订单奖励成直冲钻石奖励
* @param payId
* @param conf
*/
static replacePrizeToChongzhi(payId, conf) {
return {
id: payId,
money: conf.money,
payExp: [
{
"a": "attr",
"t": "payExp",
"n": conf.money * 10
}
],
prize: [
{
"a": "attr",
"t": "rmbmoney",
"n": conf.money * 10
}
],
firstPayPrize: [],
name: `replacePrize`,
time: -1,
buys: 0,
needVip: 0,
front: {}
}
}
/**
* 支付成功后,某些情况下会有超出限购次数的限购订单
* 这种情况需检查返回true是需要转换成钻石的订单
* @param uid
* @param payId
* @param conf
* @param payArgs
* @param player
*/
static async checkBuysAfterPay(uid, payId, conf, payArgs, player) {
if (payId == 'G123SendGift') {
let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});
if (giftInfo.purchaseLimitAmount && giftInfo.buyNumber >= giftInfo.purchaseLimitAmount && number(giftInfo.price) > 0) {
return true
}
} else {
let buyLog = await this.getPayLog(uid, payId) || [];
if (conf.buys > 0 && conf.time > 0) {
buyLog = buyLog.filter(v => v.time >= PublicShared.getToDayZeroTime(G.time));
}
if (conf.buys > 0 && buyLog.length >= conf.buys) return true;
//针对周末礼包的单独处理
if (payId.indexOf('wkdlibao') != -1) {
let call = this.getCall(player)
let conf = await zmlbGetConf(call, {payId})
if (buyLog.length && buyLog.length >= conf.buyNum) return true
}
}
return false
}
/**
* 拉起订单前检查购买前置条件,如符合,通知前端拉起支付
* 针对G123礼包有特殊处理不在游戏配置内预设的充值挡位
* todo 目前没有订单锁,所以会造成限购商品可以二次拉起支付
* @param player
* @param payId
* @param payArgs
*/
static async check(player: player, payId: string, payArgs?: any) {
let conf: any = await this.getConf(payId, payArgs);
if (!conf) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: 0});
if (player.vip < conf.needVip) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -3});
if (conf.front.cond) {
let pays = await this.getPayLogs(player.uid, conf.front.pays);
//@ts-ignore
let actives = Object.entries(pays).map(v => checkPayIsActive(v[0], v[1], payArgs)).filter(v => v);
if (conf.front.cond == '|' && actives.length < 1) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -4});
if (conf.front.cond == '&' && actives.length < conf.front.pays.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -4});
}
if (payId == 'G123SendGift') {
let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});
if (giftInfo.purchaseLimitAmount && giftInfo.buyNumber >= giftInfo.purchaseLimitAmount) {
return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
}
if (number(giftInfo.price) <= 0) {
await this.pay(player.uid, payId, payArgs, 'user');
return
}
} else {
let buyLog = await this.getPayLog(player.uid, payId) || [];
if (conf.buys > 0 && conf.time > 0) {
buyLog = buyLog.filter(v => v.time >= PublicShared.getToDayZeroTime(G.time));
}
if (conf.buys > 0 && buyLog.length >= conf.buys) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
// todo 没看懂这个判断针对什么支付不支持周末礼包这个类型的限购所以加上判断payId.indexOf('wkdlibao') == -1
if (conf.time != -1 && buyLog.slice(-1)[0]?.eTime > G.time && conf.buys == 0 && payId.indexOf('wkdlibao') == -1) {
return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -2});
}
/**
* 活动相关的自选礼包目前htype 4、14支持此方法
* payArgs里传相应参数selectList选择的奖励列表
* 往后所有自选类型走这个方法配置格式同4、14
*/
if (payArgs && payArgs?.htype && payArgs?.selectList) {
let call = this.getCall(player)
// @ts-ignore
let _hdInfo = (await HuoDongFun.gethdList(call, payArgs.htype))[0]
// 活动类购买不符合条件返回-5
if (!_hdInfo) G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -5})
let giftInfo = _hdInfo.data.gift.find(v => v.payId == payId);
if (!giftInfo) G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -5})
let dlzPrize = R.flatten(giftInfo.dlz.map(i => R.values(i)))
let selectPrize = payArgs.selectList.map(v => {
let sPrize = dlzPrize.find(o => o.a == v.a && o.t == v.t && o.n == v.n)
// 严格判断,自选奖励不存在奖励列表,返回-6
if (!sPrize) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -6});
return sPrize
})
// 自选奖励过判断后存入redis6小时内支付有效 todo 6小时可能有些长
let prize = [...giftInfo.prize, ...selectPrize]
G.ioredis.setex(`pay:${payId}:${player.uid}`, 21600, JSON.stringify(prize));
}
//针对每日礼包的单独处理
let zeroTime = PublicShared.getToDayZeroTime(G.time)
if (payId.indexOf('136Gift') != -1 && payId != '136GiftAll') {
let buyLog136 = await this.getPayLog(player.uid, '136GiftAll');
buyLog136 = buyLog136.filter(v => v.time >= zeroTime);
if (buyLog136.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
}
if (payId.indexOf('136Gift') != -1 && payId == '136GiftAll') {
let ids = R.compose(R.map(i => i.id), R.filter(i => i.id.indexOf('136Gift') != -1 && i.id != '136GiftAll'), R.values())(G.gc.pay)
let buyLog136 = await this.getPayLogs(player.uid, ids);
let buyLog136list = R.compose(R.filter(v => v.time >= zeroTime), R.flatten(), R.values())(buyLog136)
if (buyLog136list.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
}
//针对周末礼包的单独处理
if (payId.indexOf('wkdlibao') != -1) {
let call = this.getCall(player)
let conf = await zmlbGetConf(call, {payId})
if (buyLog.length && buyLog.length >= conf.buyNum) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
}
}
if (G.config.debug) {
await this.pay(player.uid, payId, payArgs, 'user', "debuger_" + new Date().getTime());
} else {
G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: 200});
}
}
static getCall(player: player): call {
return {
get otherBuff() {
return this.conn.otherBuff;
},
uid: player.uid,
conn: G.server.uid_connections[player.uid] ? G.server.uid_connections[player.uid] : {
id: null,
uid: player.uid,
gud: player,
item: null,
otherBuff: null,
heroPos: null,
refreshPower: null,
sendMsg: null,
},
service: {name: "pay"},
req: {},
eventMsg: null,
addEventMsg() {
return ApiCall.prototype.addEventMsg.call(this, ...arguments);
}
};
}
/**
* 当玩家购买过有时间期效性的礼包 并且在生效期内每天有邮件奖励时 每天首次登陆时根据时间补发邮件 玩家每天首次登陆会进入检查
* @param player
* @param lastTime 上一次登陆时间
* @param curTime 每日首次登陆时间
*/
static async checkGiftDayEmail(player: player, lastTime: number, curTime: number) {
const buyLogs = await this.getPayLogs(player.uid);
const keys = Object.keys(buyLogs).filter(payId => G.gc.payEmail[payId]?.length > 0);
const curZeroTime = PublicShared.getToDayZeroTime(curTime);
const lastZeroTime = PublicShared.getToDayZeroTime(lastTime);
keys.forEach(payId => {
let payEmailConf = G.gc.payEmail[payId] as _gcType['payEmail']['caifutequan'];
let latelyLog = buyLogs[payId].slice(-1)[0];
if (!latelyLog || (latelyLog.eTime && lastZeroTime >= latelyLog.eTime)) return;
let countDay = 1;
let payZeroTime = PublicShared.getToDayZeroTime(latelyLog.time);
let payZeroEtime = PublicShared.getToDayZeroTime(latelyLog.eTime);
let days: number
if (curZeroTime > payZeroEtime) {
days = (payZeroEtime - lastZeroTime) / (24 * 3600);
} else {
days = (curZeroTime - lastZeroTime) / (24 * 3600);
}
for (let n = 1; n <= days; n++) {
countDay++;
// if (payZeroTime + n * 24 * 3600 <= payZeroEtime) continue;
payEmailConf.forEach(conf => {
if (conf.day == 1 || countDay % conf.day == 0) {
EmailFun.addEmail({
uid: player.uid,
type: 'system',
title: conf.title,
content: conf.content,
prize: conf.prize,
createTime: lastZeroTime + n * 24 * 3600
});
}
});
}
});
}
/**
* 获取某个时间段内 玩家充值x元以上的天数
*/
static async getPayDaysBuyPayNum(uid: string, sTime: number, eTime: number, ckeckNeed: number) {
let logs = await G.mongodb.collection('dayPay').find({uid: uid}).toArray() || [];
return logs.filter(log => log.time >= sTime && log.time <= eTime && log.payNum >= ckeckNeed).length;
}
/**
* 获取某个时间段内 玩家的充值总金额
*/
static async getPayDaysAllPayNum(uid: string, sTime: number, eTime: number) {
let logs = await G.mongodb.collection('dayPay').find({
uid: uid,
time: {$gte: sTime, $lt: eTime}
}).toArray() || [];
if (logs.length <= 0) return 0;
return logs.map(log => log.payNum).reduce((a, b) => a + b);
}
}