Files
memind/scripts/compensate-user-billing.mjs
John 6d99d762da Add image-designer skill, align billing to DeepSeek ×1, and enrich Plaza demo.
Introduce AI image generation with chat shortcut and agent API, improve MindSpace chat-to-page save resolution, and seed Plaza covers with production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:29:19 -07:00

154 lines
5.4 KiB
JavaScript
Raw Permalink 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.
#!/usr/bin/env node
/**
* One-off billing compensation for duplicate charges / rate overcharge.
*
* Usage:
* node scripts/compensate-user-billing.mjs wx_mk4zzaps
* node scripts/compensate-user-billing.mjs wx_mk4zzaps --apply
* node scripts/compensate-user-billing.mjs wx_mk4zzaps --apply --include-rate-adjustment
*/
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import mysql from 'mysql2/promise';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return;
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
const value = trimmed.slice(eq + 1).trim();
if (!process.env[key]) process.env[key] = value;
}
}
loadEnvFile(path.join(root, '.env'));
const username = process.argv[2];
const apply = process.argv.includes('--apply');
const includeRateAdjustment = process.argv.includes('--include-rate-adjustment');
if (!username) {
console.error('Usage: node scripts/compensate-user-billing.mjs <username> [--apply] [--include-rate-adjustment]');
process.exit(1);
}
if (!process.env.DATABASE_URL) {
console.error('DATABASE_URL is not configured');
process.exit(1);
}
/** wx_mk4zzaps duplicate Finish billing (2026-06-16 session 20260616_600). */
const DUPLICATE_COST_CENTS = 1735;
const DUPLICATE_TOKENS = 838567;
/** Unique bills at 2/6 分/1k vs documented DeepSeek ×1 default (0.101/0.202 分/1k). */
const RATE_OVERCHARGE_CENTS = 1467;
async function main() {
const pool = mysql.createPool(process.env.DATABASE_URL);
try {
const [users] = await pool.query(
`SELECT u.id, u.username, u.display_name, u.status,
COALESCE(w.balance_cents, 0) AS balance_cents,
COALESCE(w.tokens_used, 0) AS tokens_used
FROM h5_users u
LEFT JOIN h5_user_wallets w ON w.user_id = u.id
WHERE u.username = ?
LIMIT 1`,
[username],
);
const user = users[0];
if (!user) {
console.error(`User not found: ${username}`);
process.exit(1);
}
const amountCents = DUPLICATE_COST_CENTS + (includeRateAdjustment ? RATE_OVERCHARGE_CENTS : 0);
const tokenAdjust = -DUPLICATE_TOKENS;
const note = includeRateAdjustment
? `补偿:重复扣费 ¥${(DUPLICATE_COST_CENTS / 100).toFixed(2)} + 费率差额 ¥${(RATE_OVERCHARGE_CENTS / 100).toFixed(2)}`
: `补偿:重复扣费 ¥${(DUPLICATE_COST_CENTS / 100).toFixed(2)}session 20260616_600`;
console.log('User:', user.username, user.display_name ?? '', `(${user.id})`);
console.log('Current status:', user.status);
console.log('Current balance:', `¥${(Number(user.balance_cents) / 100).toFixed(2)}`);
console.log('Current tokens_used:', Number(user.tokens_used).toLocaleString());
console.log('');
console.log('Compensation plan:');
console.log(' balance +', `¥${(amountCents / 100).toFixed(2)}`, `(${amountCents} cents)`);
if (tokenAdjust !== 0) {
console.log(' tokens_used', tokenAdjust.toLocaleString(), '(remove duplicate token count)');
}
console.log(' note:', note);
console.log(' reactivate account if suspended:', user.status === 'suspended' ? 'yes' : 'no');
if (!apply) {
console.log('');
console.log('Dry run only. Re-run with --apply to execute.');
return;
}
const now = Date.now();
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.query(
`INSERT INTO h5_user_wallets (user_id, balance_cents, tokens_used, updated_at)
VALUES (?, ?, 0, ?)
ON DUPLICATE KEY UPDATE
balance_cents = balance_cents + VALUES(balance_cents),
tokens_used = GREATEST(CAST(tokens_used AS SIGNED) + ?, 0),
updated_at = VALUES(updated_at)`,
[user.id, amountCents, now, tokenAdjust],
);
await conn.query(
`INSERT INTO h5_billing_ledger
(user_id, type, amount_cents, tokens, note, operator_id, created_at)
VALUES (?, 'adjust', ?, ?, ?, NULL, ?)`,
[user.id, amountCents, tokenAdjust, note, now],
);
if (user.status === 'suspended') {
await conn.query(`UPDATE h5_users SET status = 'active', updated_at = ? WHERE id = ?`, [
now,
user.id,
]);
}
await conn.commit();
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
const [updatedRows] = await pool.query(
`SELECT u.status, COALESCE(w.balance_cents, 0) AS balance_cents,
COALESCE(w.tokens_used, 0) AS tokens_used
FROM h5_users u
LEFT JOIN h5_user_wallets w ON w.user_id = u.id
WHERE u.id = ?
LIMIT 1`,
[user.id],
);
const updated = updatedRows[0];
console.log('');
console.log('Applied.');
console.log('New status:', updated.status);
console.log('New balance:', `¥${(Number(updated.balance_cents) / 100).toFixed(2)}`);
console.log('New tokens_used:', Number(updated.tokens_used).toLocaleString());
} finally {
await pool.end();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});