v0.0.12
✨ New Features
Go backend
- New online recharge module (
OrderTypeTopup=2):- Added
model/topup.godefiningTopupPreset,CreateTopupOrderInput,TopupOrderPlanInfo, plus business functionsCreateTopupOrder,ActivateTopupByOrder,ResolveTopupAmount,GetTopupSettings,SaveTopupSettings. - Reuses
GenerateOrderNo("TP")for recharge order numbers (prefixTP); writes them to the sameorderstable astype=2, avoiding an extra table. ActivateTopupByOrderis idempotent (paid orders return immediately); on activation it callsIncreaseUserQuotaand writesRecordTopupLog. ATODOmarks quota non-reversal on refund (awaiting unified admin order management).model.AutoMigratecreates the table at startup — no manual DDL.
- Added
- New recharge system-setting keys (
model/system_setting.go):topup.enabled(master switch),topup.allow_custom(allow custom amount),topup.presets(JSON array of[{amount, bonus_quota}, ...]),topup.exchange_rate(default1, i.e. 1 CNY = 1 quota; applies only to custom amounts).- Category constant
SystemSettingCategoryTopup = "topup".
- New HTTP endpoints:
POST /api/topup/order(CreateTopupOrder, user-auth): body{ amount, preset_amount, pay_method }; pre-validates the switch / channel / amount, then persists the order and reusescontroller/buildPayInfoforpay_url/qr_code.GET /api/setting/topup&PUT /api/setting/topup(GetTopupSettings/PutTopupSettings, root only): read/write the four keys above.
- Payment callback dispatched by order type (
controller/payment.go::processNotify):order.Type == OrderTypeTopup→ActivateTopupByOrderto credit quota.- Otherwise →
ActivatePackageByOrderto activate the plan. MockPay(admin test endpoint) dispatches the same way and surfaces "balance credited" / "plan activated" copy.
Frontend (web/default-pro/)
- New reusable component
components/TopupModal.vue:- Props:
modelValue,settings,title; emits:update:modelValue,success,error,pay. - Top "Choose amount" chip row: renders all
settings.presetsin order; the last chip is always "Custom" whensettings.allow_custom=true. The amount input is shown only after clicking Custom. - Payment-method picker (
pay-picker-item) with WeChat (#07C160) and Alipay (#1677FF) brand colors, reusing the styles fromPlans.vue. - Footer shows "Pay amount" + "Confirm recharge"; submission is disabled when Custom is selected but no amount is entered.
- Props:
- New settings view
views/setting/TopupSetting.vue(root-only): vertical layout with three sections — ① master switch (enabled), ② allow custom amount (allow_custom), ③ custom-amount 1 CNY = X quota (exchange_rate, default1); preset rows use an<a-table>with inline-editableamount/bonus_quotacolumns, plus add / delete buttons. Front-end pre-validates duplicate amounts viavalidateTopupPresetsbefore submission. - New route
/setting/topup: registered inrouter/index.js; sidebar menu entry "充值" added inSetting.vue(root-only, iconicon-subscribe-add). - New
api/topup.js:topupApi.createOrder({ amount, preset_amount, pay_method }). - Extended
api/setting.js:getTopup()/putTopup(data). - New
utils/topup.jspure-function helpers:formatNumber,formatAmount,validateTopupPresets,calcCustomBonus— usable in Node.js vianode:test. - Dashboard recharge button wired up:
Dashboard.vuenow opensTopupModalfrom the "Recharge" button on the balance card, prefetching/api/setting/topup+/api/payment/statusto give precise error copy when either is missing. The underlying<a-modal>uses:visible+@update:visible(bug fix: the previous:model-valuebinding never showed the dialog because Arco's<a-modal>prop isvisible).- Also opens a QR-code / bank-transfer modal and refreshes the balance via
/api/user/self.
- Order-management table gets an "Order type" column (
Orders.vue): two-color chips for "Plan" / "Recharge" (type-planblue,type-topuporange). - Removed the "Allow balance recharge" placeholder switch from the Operations tab (
OperationSetting.vue): that field has migrated to the new Top-up tab; the UI and backendGetPlanSettings/PutPlanSettingsno longer read or write it. The DB row is kept for backwards compatibility. - Removed the hard-coded plan-name whitelist from
Plans.vue: theVALID_PLAN_NAMES = ['lite','air','pro','max']filter was hiding test plans reported by the user; we now render whatever the backend returns.
🐛 Bug Fixes
- Dashboard "Recharge" button did nothing on click:
TopupModal.vuepreviously bound the inner<a-modal>to:model-value="modelValue", but Arco's<a-modal>visibility prop isvisible, so the dialog never showed even whentopupModalVisibleflipped totrue. Switched to:visible="modelValue"+@update:visible="(v) => emit('update:modelValue', v)". - Stale-cached settings made the recharge button appear unresponsive after admin enabled recharge: the dashboard only fetched settings when
topupSettings.valuewas null, so togglingtopup.enabledin admin did not propagate until the dashboard was hard-reloaded. Now we force-re-fetch on every click and surface clearer error copy ("contact the admin to enable it under Settings → Top-up"). <a-spin>width anomaly on TopupSetting page: added the requiredstyle="width: 100%".- Misaligned three-column form on TopupSetting: switched to
layout="vertical"so the three sections stack vertically.
🚀 Data integrity / validation
- Preset amounts cannot repeat:
model.SaveTopupSettingsuses aseenAmountsmap; duplicates return "快捷金额重复:XX 元已存在" and the whole save is rejected. The newutils/topup.js::validateTopupPresetsmirrors this check on the frontend to avoid round-tripping invalid payloads.
📚 Documentation
- New
AGENTS.mdat project root (480 lines): a single-source-of-truth guide for agents and contributors covering TL;DR / project layout / dev environment / commit conventions (Conventional Commits + bilingual body + one-file-per-commit granularity) / versioning / naming (topupis the global name) / comment conventions (4-line file header, exported-symbol comments, TODO/XXX, edit-time updates) / backend conventions (response format, model layer is HTTP-free, order-number prefixes TB/UP/TP) / frontend conventions (Arco<a-modal>uses:visiblenot:model-value,<a-spin>requiresstyle="width:100%", util tests vianode:test) / database / pitfall checklist / recommended workflow (Plan mode → per-file commit). - Synchronized
README.mdand all 7 language copies (readme/README.{en,zh-TW,ja,ko,ar,de,ru}.md): added a new "💰 Online Top-up (Quota Balance)" feature-highlight section; the existing "Orders & Real Payments" section now mentions the order-type column andprocessNotifydispatch; the "Online quota top-up" and "Top-up settings center" roadmap items moved from Planned → Done; added a new "Top-up refund loop" in-progress item awaiting unified admin order management.
🔧 Refactor / tooling
- Removed
plan.allow_topupreads/writes fromcontroller/setting_payment.go: trimmedGetPlanSettings/PutPlanSettingsresponse shape; DB row preserved. - Cleaned up debug logs from
Dashboard.vue::onRechargeClick(left over from the "recharge button does nothing" investigation).
⚠️ Upgrade Notes
- Database migration: 4 new
system_settingskeys (topup.enabled/topup.allow_custom/topup.presets/topup.exchange_rate). No migration script required —AutoMigrateplus first-write will populate them as needed. orderstable is reused: newtype=2recharge rows share the existing table withtype=1plan orders; no DDL change.- Operations tab "Plan Operations" sub-section: the placeholder switch "Allow balance recharge" has been removed from the UI. Use Settings → Top-up instead.
- Recharge is off by default (
topup.enabledisfalseuntil an admin toggles it). The dashboard button will not open the dialog until an admin enables recharge and configures at least one preset (or allows custom amounts). - Refunds are not supported in this release: recharge orders with
status=3are merely flagged; quota is not reversed. The full refund loop will land with the unified admin order-management feature. - Frontend build:
qrcodeis now a runtime dependency (already inpnpm-lock.yaml). Recommendpnpm install && npm run buildbefore publishing.