koishi-plugin-prism 0.1.23 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/index.js +86 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ pricingConfigIds: [pricing-mahjong-a]
|
|
|
56
56
|
* `login` / `入场` - 开启当前玩家的计费场次。
|
|
57
57
|
* `logout` - 结算当前玩家的计费场次。
|
|
58
58
|
玩家在未产生任何费用时退场,机器人会简洁显示“本次未产生费用”和当前余额;存在收费或优惠明细时仍显示完整结算账单。
|
|
59
|
+
结账成功回执中的余额为后端已经完成扣款后的余额;只有 `/billing` 预览会显示当前余额与预计结账后余额。
|
|
60
|
+
方案内区间封顶直接计入对应计时费用;全局封顶按日期和时段逐条列出,不合并、不截断,并直接形成计费总价,不作为优惠。只作用于整次结账的资产优惠直接列在计费总价下方,不显示额外标题或 emoji,也不会误归属到最后一个 session。
|
|
59
61
|
结账成功后,机器人会向 `staffUserIds` 与 `logoutNotifyUserIds` 中的用户私聊同一份账单,账单会明确显示结账玩家身份。
|
|
60
62
|
同一玩家在前一次退场结账尚未完成时重复发送 `/logout` 或 `/退场`,机器人会复用同一次结账请求与账单,避免重复扣款。
|
|
61
63
|
账单和管理员代操作回执均使用“平台昵称(QQ:号码)”称呼玩家;平台暂时无法提供昵称时显示“未知昵称(QQ:号码)”,不会显示内部玩家 ID。
|
package/lib/index.js
CHANGED
|
@@ -510,22 +510,33 @@ class PrismKoishiService {
|
|
|
510
510
|
const result = (await this.client.confirmCheckoutByIdentity(this.identity(sender), false));
|
|
511
511
|
const settlement = result?.playerSettlement ?? result?.settlement ?? {};
|
|
512
512
|
const records = result?.settlements ?? [];
|
|
513
|
+
const checkoutAdjustments = (result?.checkoutAdjustments ?? []);
|
|
514
|
+
const pricingCapAdjustments = (result?.pricingCapAdjustments ?? []);
|
|
515
|
+
const checkoutAdjustmentKeys = new Set(checkoutAdjustments.map(adjustmentKey));
|
|
516
|
+
const pricingCapAdjustmentKeys = new Set(pricingCapAdjustments.map(adjustmentKey));
|
|
513
517
|
const sessionPreviews = records.map((rec) => {
|
|
514
518
|
const s = rec?.settlement ?? {};
|
|
519
|
+
const sessionAdjustments = (rec?.adjustments ?? []).filter((adjustment) => {
|
|
520
|
+
const key = adjustmentKey(adjustment);
|
|
521
|
+
return !checkoutAdjustmentKeys.has(key) &&
|
|
522
|
+
!pricingCapAdjustmentKeys.has(key) &&
|
|
523
|
+
!isPricingCapAdjustment(adjustment);
|
|
524
|
+
});
|
|
525
|
+
const sessionSubtotal = toNumber(s.subtotal ?? 0);
|
|
515
526
|
return {
|
|
516
527
|
sessionId: s.sessionId,
|
|
517
528
|
label: s.label,
|
|
518
529
|
startedAt: s.startedAt,
|
|
519
530
|
endedAt: s.endedAt ?? s.settledAt,
|
|
520
531
|
status: "closed",
|
|
521
|
-
subtotal:
|
|
522
|
-
total:
|
|
532
|
+
subtotal: sessionSubtotal,
|
|
533
|
+
total: Math.max(0, sessionSubtotal + sessionAdjustments.reduce((sum, adjustment) => sum + toNumber(adjustment?.amount ?? 0), 0)),
|
|
523
534
|
chargeItems: rec?.chargeItems ?? [],
|
|
524
|
-
adjustments:
|
|
535
|
+
adjustments: sessionAdjustments,
|
|
525
536
|
};
|
|
526
537
|
});
|
|
527
538
|
const synthetic = {
|
|
528
|
-
|
|
539
|
+
settlement: {
|
|
529
540
|
playerId: settlement.playerId,
|
|
530
541
|
subtotal: settlement.subtotal ?? 0,
|
|
531
542
|
total: settlement.total ?? 0,
|
|
@@ -533,6 +544,9 @@ class PrismKoishiService {
|
|
|
533
544
|
sessionPreviews,
|
|
534
545
|
chargeItems: result?.chargeItems ?? [],
|
|
535
546
|
adjustments: result?.adjustments ?? [],
|
|
547
|
+
checkoutAdjustments,
|
|
548
|
+
pricingCapAdjustments,
|
|
549
|
+
globalCapWindows: result?.globalCapWindows ?? [],
|
|
536
550
|
assetHoldings: result?.assetHoldings ?? [],
|
|
537
551
|
};
|
|
538
552
|
const receipt = await this.formatCheckoutPreview(synthetic, sender, "✅ 退场成功 · 结算账单");
|
|
@@ -892,7 +906,10 @@ class PrismKoishiService {
|
|
|
892
906
|
},
|
|
893
907
|
];
|
|
894
908
|
}
|
|
895
|
-
const adjustments = result?.adjustments ?? [];
|
|
909
|
+
const adjustments = (result?.adjustments ?? []);
|
|
910
|
+
const pricingCapAdjustments = (result?.pricingCapAdjustments ?? adjustments.filter(isPricingCapAdjustment));
|
|
911
|
+
const sessionAdjustmentKeys = new Set(sessionPreviews.flatMap((session) => (session?.adjustments ?? []).map(adjustmentKey)));
|
|
912
|
+
const checkoutAdjustments = (result?.checkoutAdjustments ?? adjustments.filter((adjustment) => !isPricingCapAdjustment(adjustment) && !sessionAdjustmentKeys.has(adjustmentKey(adjustment))));
|
|
896
913
|
const assetHoldings = result?.assetHoldings ?? [];
|
|
897
914
|
const lines = [];
|
|
898
915
|
lines.push(title);
|
|
@@ -907,7 +924,7 @@ class PrismKoishiService {
|
|
|
907
924
|
}
|
|
908
925
|
}
|
|
909
926
|
const hasNonZeroSessionTotal = sessionPreviews.some((session) => toNumber(session?.total ?? 0) !== 0);
|
|
910
|
-
const hasNonZeroAdjustment = hasAdjustmentEntries(
|
|
927
|
+
const hasNonZeroAdjustment = hasAdjustmentEntries(checkoutAdjustments, sessionPreviews);
|
|
911
928
|
if (!hasNonZeroSessionTotal && !hasNonZeroAdjustment) {
|
|
912
929
|
lines.push("");
|
|
913
930
|
lines.push("本次未产生费用");
|
|
@@ -947,10 +964,51 @@ class PrismKoishiService {
|
|
|
947
964
|
lines.push(` └ ${adjLabel}:${formatNumber(amount)}${currency}`);
|
|
948
965
|
}
|
|
949
966
|
}
|
|
967
|
+
const cappedWindows = (result?.globalCapWindows ?? []).filter((window) => toNumber(window?.currentAmount) !== toNumber(window?.amountApplied));
|
|
968
|
+
const appliedCapAdjustments = pricingCapAdjustments.filter((adjustment) => toNumber(adjustment?.amount ?? 0) !== 0);
|
|
969
|
+
if (cappedWindows.length > 0 || appliedCapAdjustments.length > 0) {
|
|
970
|
+
lines.push("");
|
|
971
|
+
lines.push("封顶:");
|
|
972
|
+
if (cappedWindows.length > 0) {
|
|
973
|
+
for (const window of cappedWindows) {
|
|
974
|
+
const label = firstDefined(window, "ruleLabel", "label", "name") ?? "封顶时段";
|
|
975
|
+
const startedAt = parseDateTime(window?.windowStartedAt);
|
|
976
|
+
const datedLabel = startedAt ? `${formatMD(startedAt)} ${label}` : label;
|
|
977
|
+
const currentAmount = toNumber(window?.currentAmount);
|
|
978
|
+
const amountApplied = toNumber(window?.amountApplied);
|
|
979
|
+
const priceCap = toNumber(window?.priceCap);
|
|
980
|
+
const paidBefore = toNumber(window?.paidBefore);
|
|
981
|
+
const details = [`上限${formatNumber(priceCap)}`];
|
|
982
|
+
if (paidBefore > 0)
|
|
983
|
+
details.push(`已计${formatNumber(paidBefore)}`);
|
|
984
|
+
lines.push(`- ${datedLabel}:${formatNumber(currentAmount)} → ${formatNumber(amountApplied)}${currency}(${details.join(",")})`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
for (const adjustment of appliedCapAdjustments) {
|
|
989
|
+
const label = firstDefined(adjustment, "label", "name", "source") ?? "封顶时段";
|
|
990
|
+
lines.push(`- ${label}:计费调整 ${formatNumber(adjustment?.amount ?? 0)}${currency}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const visibleCheckoutAdjustments = checkoutAdjustments.filter((adjustment) => toNumber(firstDefined(adjustment ?? {}, "amount", "saved", 0)) !== 0);
|
|
950
995
|
lines.push("");
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
996
|
+
const cappedTotal = Math.max(0, toNumber(subtotal) + pricingCapAdjustments.reduce((sum, adjustment) => sum + toNumber(adjustment?.amount ?? 0), 0));
|
|
997
|
+
lines.push(`计费总价:${formatNumber(cappedTotal)}${currency}`);
|
|
998
|
+
const hasManualAdjustment = visibleCheckoutAdjustments.some((adjustment) => cleanText(adjustment?.source).startsWith("staff.override:"));
|
|
999
|
+
if (visibleCheckoutAdjustments.length > 0) {
|
|
1000
|
+
lines.push("");
|
|
1001
|
+
for (const adjustment of visibleCheckoutAdjustments) {
|
|
1002
|
+
const amount = toNumber(firstDefined(adjustment ?? {}, "amount", "saved", 0));
|
|
1003
|
+
const label = firstDefined(adjustment ?? {}, "label", "name", "source") ?? "优惠";
|
|
1004
|
+
lines.push(`${label}:${formatNumber(amount)}${currency}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (hasNonZeroAdjustment) {
|
|
1008
|
+
if (visibleCheckoutAdjustments.length > 0)
|
|
1009
|
+
lines.push("");
|
|
1010
|
+
lines.push(`${hasManualAdjustment ? "调整后价格" : "优惠后价格"}:${formatNumber(total)}${currency}`);
|
|
1011
|
+
}
|
|
954
1012
|
if (hasBalance) {
|
|
955
1013
|
const isPreview = result?.settlementPreview != null && result?.settlement == null;
|
|
956
1014
|
if (isPreview) {
|
|
@@ -1137,6 +1195,21 @@ function formatPlayerReference(sender, provider = "qq") {
|
|
|
1137
1195
|
const name = sender.name && sender.name !== sender.id ? sender.name : "未知昵称";
|
|
1138
1196
|
return `${name}(${provider.toUpperCase()}:${sender.id})`;
|
|
1139
1197
|
}
|
|
1198
|
+
function adjustmentKey(adjustment) {
|
|
1199
|
+
const id = cleanText(adjustment?.id);
|
|
1200
|
+
if (id)
|
|
1201
|
+
return `id:${id}`;
|
|
1202
|
+
return JSON.stringify([
|
|
1203
|
+
adjustment?.source ?? "",
|
|
1204
|
+
adjustment?.label ?? "",
|
|
1205
|
+
toNumber(adjustment?.amount ?? 0),
|
|
1206
|
+
]);
|
|
1207
|
+
}
|
|
1208
|
+
function isPricingCapAdjustment(adjustment) {
|
|
1209
|
+
return adjustment?.pricingCapHistory != null ||
|
|
1210
|
+
cleanText(adjustment?.source).startsWith("time.cap:") ||
|
|
1211
|
+
cleanText(adjustment?.id).startsWith("time-cap:");
|
|
1212
|
+
}
|
|
1140
1213
|
function toNumber(value) {
|
|
1141
1214
|
if (value == null)
|
|
1142
1215
|
return 0;
|
|
@@ -1197,6 +1270,10 @@ function formatHM(dt) {
|
|
|
1197
1270
|
const pad = (n) => String(n).padStart(2, "0");
|
|
1198
1271
|
return `${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
|
|
1199
1272
|
}
|
|
1273
|
+
function formatMD(dt) {
|
|
1274
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1275
|
+
return `${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}`;
|
|
1276
|
+
}
|
|
1200
1277
|
function formatDateTime(value) {
|
|
1201
1278
|
const dt = parseDateTime(value);
|
|
1202
1279
|
if (!dt)
|