407 lines
17 KiB
TypeScript
407 lines
17 KiB
TypeScript
/// <reference types="@javatypes/bungee-api" />
|
|
/// <reference types="@javatypes/bukkit-api" />
|
|
/// <reference types="@javatypes/sponge-api" />
|
|
|
|
import { plugin, server, task } from '@ccms/api'
|
|
import { Autowired, JSClass } from '@ccms/container'
|
|
import { Cmd, Config, interfaces, JSPlugin, Listener, PluginConfig, Tab } from '@ccms/plugin'
|
|
|
|
import type { MiaoReward } from './MiaoReward'
|
|
import http from '@ccms/common/dist/http'
|
|
import * as CryptoJS from "crypto-js"
|
|
|
|
const Thread = java.lang.Thread
|
|
|
|
interface PlayerPointsAPI {
|
|
look(name: string)
|
|
give(name: string, amount: number)
|
|
take(name: string, amount: number)
|
|
}
|
|
|
|
interface Order {
|
|
order_id: string
|
|
amount: number
|
|
url?: string
|
|
}
|
|
interface Sync {
|
|
scaned: boolean
|
|
start?: number
|
|
left?: number
|
|
cancelled?: boolean
|
|
paying?: boolean
|
|
}
|
|
|
|
interface PlaceholderAPI {
|
|
registerPlaceholderHook: (key: string, onPlaceholderRequest: (player, s) => string) => void
|
|
unregisterPlaceholderHook: (key: string) => void
|
|
setPlaceholders: (player: any, str: string) => string
|
|
}
|
|
|
|
const defaultConfig = {
|
|
prefix: '§6[§b喵式支付§6]',
|
|
name: '',
|
|
id: '',
|
|
secret: '',
|
|
command: 'points give %player_name% %amount%',
|
|
check: '%playerpoints_points%',
|
|
ratio: 100,
|
|
coinName: '点券',
|
|
reward: {
|
|
'*': [
|
|
'msg %player_name% 充值 %amount% 点券成功...'
|
|
],
|
|
10: [
|
|
"points give %player_name% 1",
|
|
'msg %player_name% 充值 10 点券 奖励 1 点券...'
|
|
],
|
|
300: [
|
|
"points give %player_name% 15",
|
|
'msg %player_name% 充值 300 点券 奖励 15 点券...'
|
|
],
|
|
500: [
|
|
"points give %player_name% 25",
|
|
'msg %player_name% 充值 500 点券 奖励 25 点券...'
|
|
],
|
|
680: [
|
|
"points give %player_name% 35",
|
|
'msg %player_name% 充值 680 点券 奖励 35 点券...'
|
|
],
|
|
1280: [
|
|
"points give %player_name% 70",
|
|
'msg %player_name% 充值 1280 点券 奖励 70 点券...'
|
|
],
|
|
}
|
|
}
|
|
|
|
@JSPlugin({ version: '1.3.5', author: 'MiaoWoo', source: __filename, depends: ['MiaoReward'], nativeDepends: ['PlaceholderAPI'] })
|
|
export class MiaoPay extends interfaces.Plugin {
|
|
@Autowired()
|
|
private server: server.Server
|
|
@Autowired()
|
|
private taskManager: task.TaskManager
|
|
@Autowired()
|
|
private pluginManager: plugin.PluginManager
|
|
|
|
@JSClass('me.clip.placeholderapi.PlaceholderAPI')
|
|
private PlaceholderAPI: PlaceholderAPI
|
|
|
|
private apiGateWay = "https://pay.yumc.pw"
|
|
private MiaoReward: MiaoReward
|
|
|
|
private cacheMap = new Map<string, Order>();
|
|
private cacheSyncMap = new Map<string, Sync>();
|
|
|
|
@Config()
|
|
private config: PluginConfig & typeof defaultConfig = defaultConfig
|
|
|
|
load() {
|
|
let needSave = false
|
|
for (const key of Object.keys(defaultConfig)) {
|
|
if (!this.config[key]) {
|
|
this.config[key] = defaultConfig[key]
|
|
needSave = true
|
|
}
|
|
}
|
|
needSave && this.config.save()
|
|
}
|
|
|
|
enable() {
|
|
this.MiaoReward = this.pluginManager.getPlugin('MiaoReward') as MiaoReward
|
|
if (!this.MiaoReward) { return this.logger.error('当前脚本插件需要 MiaoReward 作为前置脚本插件!') }
|
|
if (!this.config.id || !this.config.secret) { return this.logger.console('§4尚未配置商户信息 将无法正常收款!') }
|
|
}
|
|
|
|
disable() {
|
|
this.cacheMap.forEach((v, k) => {
|
|
this.MiaoReward.cancelTask(this.server.getPlayer(k))
|
|
})
|
|
this.cacheMap.clear()
|
|
}
|
|
|
|
@Cmd({ autoMain: true })
|
|
mpay() { }
|
|
|
|
cmdpay(sender: org.bukkit.entity.Player, amount: number = 0) {
|
|
if (!sender.getItemInHand) { return this.logger.sender(sender, '§4控制台无法执行此命令!') }
|
|
if (!this.MiaoReward.serverInfo) { return this.logger.sender(sender, '§4当前服务器尚未配置 请联系管理员先配置MiaoReward!') }
|
|
if (!this.config.id || !this.config.secret) { return this.logger.sender(sender, '§c当前服务器尚未配置 请联系管理员配置支付密钥!') }
|
|
if (this.cacheMap.has(sender.getName())) {
|
|
this.logger.sender(sender, '§c您有一笔订单尚未完成 请完成支付或等待订单超时!')
|
|
let sync = this.cacheSyncMap.get(sender.getName())
|
|
if (!sync.cancelled) { return }
|
|
sync.scaned = false
|
|
sync.left = (sync.paying ? 100 : 55) - (Math.round(Date.now() / 1000) - sync.start)
|
|
let order = this.cacheMap.get(sender.getName())
|
|
this.MiaoReward.setItemAndTp(sender, order.url, sync, `充值 ${this.config.ratio * order.amount} ${this.config.coinName}`, `支付宝/微信/QQ 扫码支付`)
|
|
return
|
|
}
|
|
if (amount < 1) { return this.logger.sender(sender, `§c充值异常 §4充值金额不得小于 1 ${this.config.coinName}!`) }
|
|
if (amount / this.config.ratio > 1000) { return this.logger.sender(sender, `§c充值异常 §4充值金额不得大于 ${this.config.ratio * 1000} ${this.config.coinName}!`) }
|
|
if (amount != Math.round(amount)) { return this.logger.sender(sender, `§c充值异常 §4充值金额必须为整数!`) }
|
|
try {
|
|
this.getPlayerAmount(sender)
|
|
} catch (error) {
|
|
return this.logger.sender(sender, error.message || error)
|
|
}
|
|
this.MiaoReward.sendTitle(sender, `§6充值 §a${amount} §6${this.config.coinName}`, '§c正在请求充值二维码 请稍候...')
|
|
let sync: any = { scaned: false, start: Math.round(Date.now() / 1000) }
|
|
let order = this.createOrder(sender, amount)
|
|
this.cacheMap.set(sender.getName(), order)
|
|
this.cacheSyncMap.set(sender.getName(), sync)
|
|
let order_id = order.order_id
|
|
this.MiaoReward.setItemAndTp(sender, order.url, sync, `充值 ${amount} ${this.config.coinName}`, `支付宝/微信/QQ 扫码支付`)
|
|
this.logger.sender(sender, [`§3请使用 §b支付宝§3/§a微信§3/§5QQ §3扫描二维码支付!`, `§c如未显示二维码 请打开下列网址完成充值!`, `§6地址: §3${order.url}`])
|
|
this.taskManager.create(() => {
|
|
try {
|
|
let status = this.queryStatus(order_id, 0, 55)
|
|
if (status.code != 200) { throw new Error('§c扫码超时 请重新充值!') }
|
|
this.MiaoReward.sendTitle(sender, '§a已扫码', `§3订单已创建 请及时支付!`)
|
|
this.MiaoReward.sendActionBar(sender, '§6订单号: §3' + order_id)
|
|
sync.scaned = true
|
|
sync.start = Math.round(Date.now() / 1000)
|
|
sync.paying = true
|
|
status = this.queryStatus(order_id, 1, 100)
|
|
if (status.code != 200) { throw new Error('§c支付超时 请重新充值!') }
|
|
if (status.data == 2) {
|
|
this.MiaoReward.sendTitle(sender, '§a已支付', `§3订单已支付 请等待充值到账...`)
|
|
this.MiaoReward.sendActionBar(sender, '§6订单号: §3' + order_id)
|
|
this.logger.sender(sender, `§6订单号: §3${order_id} §a支付成功!`)
|
|
this.recharge(sender, order)
|
|
}
|
|
} catch (error) {
|
|
let cacheOrder = this.cacheMap.get(sender.getName())
|
|
if (cacheOrder && cacheOrder.order_id == order_id) {
|
|
this.MiaoReward.sendTitle(sender, '§4充值失败', error.message)
|
|
}
|
|
} finally {
|
|
sync.scaned = true
|
|
this.cacheMap.delete(sender.getName())
|
|
this.cacheSyncMap.delete(sender.getName())
|
|
}
|
|
}).async().submit()
|
|
this.MiaoReward.clearTitle(sender)
|
|
}
|
|
|
|
cmdquery(sender: org.bukkit.entity.Player, id: string) {
|
|
if (!id) { return this.logger.sender(sender, '§c请输入订单号!') }
|
|
this.taskManager.create(() => {
|
|
let result = this.queryOrder(id, sender.getName(), sender.getUniqueId().toString())
|
|
if (result.code != 200) { return this.logger.sender(sender, `§c查询异常! §4ERROR: ${result.msg}`) }
|
|
let order = result.data
|
|
this.logger.sender(sender, [
|
|
`§6商户名称: §3${order.appname}`,
|
|
`§6订单号: §3${id}`,
|
|
`§6商品: §b${order.subject}`,
|
|
`§6金额: §e${order.amount}`,
|
|
`§6玩家: §a${order.username}`,
|
|
`§6状态: §c${order.status}`,
|
|
])
|
|
if (order.status > 1 && order.status < 4) {
|
|
this.logger.sender(sender, `§3当前订单已支付 尚未完成充值 开始补单操作...`)
|
|
this.recharge(sender, order)
|
|
}
|
|
}).async().submit()
|
|
}
|
|
|
|
cmdcheck(sender: org.bukkit.entity.Player, force = 1) {
|
|
this.logger.sender(sender, `§3正在检查需要补单充值的订单 请稍候...`)
|
|
this.taskManager.create(() => {
|
|
let result = this.queryUnconverted(sender.getName(), force)
|
|
if (result.code != 200) { return this.logger.sender(sender, `§c订单查询失败: ${result.msg}`) }
|
|
let unconverteds = result.data
|
|
if (!unconverteds.length) { return this.logger.sender(sender, `§c未发现需要进行补单充值的订单!`) }
|
|
this.logger.sender(sender, `§3发现 §a${unconverteds.length}笔 §3未充值订单 §c正在充值 请稍候...`)
|
|
for (const unconverted of unconverteds) {
|
|
this.logger.sender(sender, `§3正在处理订单 §a${unconverted.order_id} §3请稍候...`)
|
|
this.recharge(sender, unconverted)
|
|
Thread.sleep(300)
|
|
}
|
|
}).async().submit()
|
|
}
|
|
|
|
/**
|
|
* 请在异步线程执行此方法
|
|
* @param amount 订单金额(非点券金额)
|
|
*/
|
|
recharge(sender: org.bukkit.entity.Player, order: Order) {
|
|
let order_id = order.order_id
|
|
let amount = order.amount
|
|
let point = this.safeMultiply(amount, this.config.ratio)
|
|
let finish = this.preFinishOrder(order_id)
|
|
if (finish.code != 200) {
|
|
this.sendError(sender, order_id, amount, '§4充值预标记异常!')
|
|
return this.logger.console(`§c充值系统异常 订单 §3${order_id} 预标记异常! §4${this.config.coinName}已停止充值 §c请手动补单!`)
|
|
}
|
|
this.taskManager.callSyncMethod(() => {
|
|
let prePoint = this.getPlayerAmount(sender)
|
|
let command = this.config.command.replace('%player_name%', sender.getName()).replace('%amount%', `${point}`).replace('%remark%', `${order_id}`)
|
|
if (!this.server.dispatchConsoleCommand(command)) {
|
|
return this.sendError(sender, order_id, amount, '§4充值命令执行异常!')
|
|
}
|
|
this.checkRecharge(sender, order_id, amount, prePoint, point)
|
|
})
|
|
}
|
|
|
|
@Listener()
|
|
private PlayerJoinEvent(event: org.bukkit.event.player.PlayerJoinEvent) {
|
|
const player = event.getPlayer()
|
|
this.cmdcheck(player, 0)
|
|
}
|
|
|
|
private checkRecharge(sender: org.bukkit.entity.Player, order_id: string, amount: number, prePoint: number, point: number) {
|
|
this.taskManager.create(() => {
|
|
let nowPoint = this.checkNowPoint(sender, point, prePoint)
|
|
if (nowPoint === false) {
|
|
return this.sendError(sender, order_id, amount, '§4充值结果检测异常!')
|
|
}
|
|
this.logger.sender(sender, [
|
|
`§6充值 §a${point} §6${this.config.coinName} §a成功 §6当前账户余额: §3${nowPoint} §6${this.config.coinName}`,
|
|
`§c如出现未到账的情况 请联系管理员!`
|
|
])
|
|
this.rewardOrder(sender, order_id, point)
|
|
let finish = this.finishOrder(order_id)
|
|
if (finish.code != 200) {
|
|
return this.logger.console(`§c充值系统异常 订单 §3${order_id} 完成标记异常! §4${this.config.coinName}可能重复到账!`)
|
|
}
|
|
}).async().submit()
|
|
}
|
|
|
|
private checkNowPoint(sender: org.bukkit.entity.Player, point: number, prePoint: number) {
|
|
let nowPoint = this.getPlayerAmount(sender)
|
|
if (nowPoint == prePoint + point) {
|
|
return nowPoint
|
|
}
|
|
Thread.sleep(100)
|
|
nowPoint = this.getPlayerAmount(sender)
|
|
if (nowPoint == prePoint + point) {
|
|
return nowPoint
|
|
}
|
|
Thread.sleep(200)
|
|
nowPoint = this.getPlayerAmount(sender)
|
|
if (nowPoint == prePoint + point) {
|
|
return nowPoint
|
|
}
|
|
Thread.sleep(300)
|
|
nowPoint = this.getPlayerAmount(sender)
|
|
return false
|
|
}
|
|
|
|
private rewardOrder(sender, order_id, point) {
|
|
this.taskManager.callSyncMethod(() => {
|
|
try {
|
|
if (this.config.reward['*']) {
|
|
let rewardCommands: string[] = this.config.reward['*']
|
|
for (const command of rewardCommands) {
|
|
this.dispatchConsoleCommand(sender, command, point, order_id)
|
|
}
|
|
}
|
|
let rewardCommands: string[] = this.config.reward[point]
|
|
if (rewardCommands && rewardCommands.length) {
|
|
for (const command of rewardCommands) {
|
|
this.dispatchConsoleCommand(sender, command, point, order_id)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('§4充值奖励命令执行错误: §c' + error)
|
|
console.ex(error)
|
|
}
|
|
})
|
|
}
|
|
|
|
private dispatchConsoleCommand(sender, command, point, order_id) {
|
|
return this.server.dispatchConsoleCommand(command.replace('%player_name%', sender.getName()).replace('%amount%', `${point}`).replace('%remark%', `${order_id}`))
|
|
}
|
|
|
|
sendError(sender: org.bukkit.entity.Player, order_id: string, amount: number, error: string) {
|
|
return this.logger.sender(sender, [
|
|
`§c========== ${this.config.prefix}§4充值异常 §c==========`,
|
|
`§6异常订单: §3${order_id}`,
|
|
`§6订单金额: §3${amount}`,
|
|
`§6异常原因: §4${error}`,
|
|
`§6异常账号: §b${sender.getName()}`,
|
|
`§6异常时间: §a${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
|
|
`§c如果已付款但${this.config.coinName}未到账 请截图发给腐竹!`,
|
|
`§c========== ${this.config.prefix}§4充值异常 §c==========`,
|
|
])
|
|
}
|
|
|
|
@Tab()
|
|
tabmpay() { }
|
|
|
|
private safeMultiply(a: number, b: number) {
|
|
return parseFloat((a * b).toFixed(0))
|
|
}
|
|
|
|
private getPlayerAmount(sender: any): number {
|
|
let result = this.PlaceholderAPI.setPlaceholders(sender, this.config.check)
|
|
let amount = parseFloat(result)
|
|
if (isNaN(amount)) {
|
|
throw new Error(`§c读取玩家 §3${this.config.coinName} §c异常 §6请检查 §3check §6配置是否正确!
|
|
§6数据解析链路: §3${this.config.check} §6=> §3${result} §6=> §3${amount}`)
|
|
}
|
|
return amount
|
|
}
|
|
|
|
private queryStatus(id: string, wait: number, time = 60) {
|
|
return this.httpPost('/status', { id, wait, time })
|
|
}
|
|
|
|
private preFinishOrder(id: string) {
|
|
return this.httpPost('/preFinish', { id })
|
|
}
|
|
|
|
private finishOrder(id: string) {
|
|
return this.httpPost('/finish', { id })
|
|
}
|
|
|
|
private createOrder(sender: org.bukkit.entity.Player, amount: number): Order {
|
|
let serverName = this.MiaoReward.serverInfo.name
|
|
if (this.config.name) { serverName = `${serverName}(${this.config.name})` }
|
|
let result = this.httpPost('/create', {
|
|
subject: `${serverName} 充值 ${amount} ${this.config.coinName}`,
|
|
amount: amount / this.config.ratio,
|
|
username: sender.getName(),
|
|
unionId: sender.getUniqueId().toString()
|
|
})
|
|
if (result.code != 200) {
|
|
throw new Error(`订单创建失败: ${result.msg}`)
|
|
}
|
|
return result.data
|
|
}
|
|
|
|
private queryOrder(id: string, username: string, uuid: string) {
|
|
return this.httpPost('/query', { id, username, uuid })
|
|
}
|
|
|
|
private queryUnconverted(username: string, force: number) {
|
|
return this.httpPost('/unconverted', { username, force })
|
|
}
|
|
|
|
private httpPost(method: string, data: any) {
|
|
let startTime = Date.now()
|
|
data.appid = this.config.id
|
|
data.timestamp = Math.round(Date.now() / 1000)
|
|
data.sign = this.sign(data)
|
|
let url = `${this.apiGateWay}/api${method}`
|
|
let result = http.post(url, data)
|
|
console.debug(`
|
|
====== HTTP POST ======
|
|
REQUEST URL : ${url}
|
|
REQUEST DATA: ${JSON.stringify(data)}
|
|
RESPONSE : ${JSON.stringify(result)}
|
|
CAST TIME : ${Date.now() - startTime}`)
|
|
return result
|
|
}
|
|
|
|
private http_build_query(params: any) {
|
|
return Object.keys(params).filter(key => key !== 'sign' && params[key] != undefined && params[key] != null)
|
|
.sort().map(key => key + '=' + params[key]).join('&')
|
|
}
|
|
|
|
private sign(params) {
|
|
// 排序后转换为字符串
|
|
let signStr = `${this.http_build_query(params)}&key=${this.config.secret}`
|
|
return CryptoJS.MD5(signStr).toString().toUpperCase()
|
|
}
|
|
}
|