feat: wave 2 migration (P3, P5, P7 openspec changes)
Implements, verifies, and archives the three remaining Wave 2 changes from openspec/MIGRATION-PLAN.md. - add-wallet-settlement (P3): demo recharge, guarded withdrawal freeze and one-time admin review, paginated own fund entries, idempotent per-shop weekly/monthly settlement statements with commission rate and one-time payout confirmation. - add-merchant-onboarding (P5): personal/enterprise applications with one live application per user, guarded review with mandatory rejection reason, and transactional shop + owner provisioning returning one-time credentials; mall onboarding/status pages and an admin review console. - add-membership-messaging (P7): platform member levels, append-only growth accrual on order completion with guarded one-way leveling, order/shipment/ refund system messages with unread/read state and soft deletion, plus the mall header unread badge. Backend: migrations 0019-0023, new wallet, settlement, merchant_onboarding, membership and messaging modules, event hooks in order/fulfillment/aftersale, and integration suites for each. Shared contract extended and all three frontends updated; code indexes, domain docs, backend guidelines and the migration tracker synced. Verification: cargo test -p vmall-api green twice consecutively; mall, admin and shop-admin builds pass; browser smoke on every new surface; openspec validate --all --strict green (33 passed). The three changes share the @vmall/shared contract, the mall mock adapter and per-app locale/nav files, so they are committed together to keep every commit buildable.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
// Buyer membership center: level, growth total, progress and growth history.
|
||||
export default {
|
||||
en: {
|
||||
membership: {
|
||||
title: "Membership",
|
||||
entry: "My level",
|
||||
loadFailed: "Unable to load your membership.",
|
||||
currentLevel: "Current level",
|
||||
benefits: "Level benefits",
|
||||
growthTotal: "Growth total",
|
||||
growthUnit: "growth",
|
||||
noLevel: "No level yet",
|
||||
noLevelHint: "Reach {threshold} {unit} to unlock {level}.",
|
||||
topLevel: "Top level reached",
|
||||
topLevelHint: "You are at the highest level — your benefits stay as they are.",
|
||||
progressTitle: "Progress to {level}",
|
||||
progressThreshold: "{current} / {threshold} {unit}",
|
||||
remaining: "{remaining} {unit} to go",
|
||||
historyTitle: "Growth history",
|
||||
historyEmpty: "No growth entries yet.",
|
||||
historyFailed: "Unable to load growth history.",
|
||||
historyDate: "Time",
|
||||
historyReason: "Reason",
|
||||
historyDelta: "Growth",
|
||||
historyTotal: "Total after",
|
||||
reason_order_complete: "Order completed",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
membership: {
|
||||
title: "会员中心",
|
||||
entry: "我的等级",
|
||||
loadFailed: "会员信息加载失败。",
|
||||
currentLevel: "当前等级",
|
||||
benefits: "等级权益",
|
||||
growthTotal: "成长值",
|
||||
growthUnit: "成长值",
|
||||
noLevel: "暂无等级",
|
||||
noLevelHint: "再获得 {threshold} {unit} 即可解锁 {level}。",
|
||||
topLevel: "已是最高等级",
|
||||
topLevelHint: "您已达到最高等级,权益保持不变。",
|
||||
progressTitle: "距离 {level}",
|
||||
progressThreshold: "{current} / {threshold} {unit}",
|
||||
remaining: "还差 {remaining} {unit}",
|
||||
historyTitle: "成长值明细",
|
||||
historyEmpty: "暂无成长值记录。",
|
||||
historyFailed: "成长值明细加载失败。",
|
||||
historyDate: "时间",
|
||||
historyReason: "原因",
|
||||
historyDelta: "成长值",
|
||||
historyTotal: "变动后累计",
|
||||
reason_order_complete: "订单完成",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
// Merchant onboarding: application form and applicant status page.
|
||||
export default {
|
||||
en: {
|
||||
merchant: {
|
||||
joinTitle: "Sell on VMall",
|
||||
joinSubtitle:
|
||||
"Submit a merchant onboarding application. The platform team reviews it and provisions your shop and owner account.",
|
||||
stepKind: "Entity type",
|
||||
stepEntity: "Entity & categories",
|
||||
stepContact: "Contact & qualification",
|
||||
kindPersonal: "Individual",
|
||||
kindEnterprise: "Enterprise",
|
||||
kindHint: "Choose the entity type that will own the shop. Required fields change with it.",
|
||||
entityInfo: "Entity information",
|
||||
realName: "Legal name",
|
||||
realNamePlaceholder: "Name shown on the identity document",
|
||||
companyName: "Company name",
|
||||
companyNamePlaceholder: "Registered company name",
|
||||
businessLicenseNo: "Business license number",
|
||||
businessLicenseNoPlaceholder: "Unified social credit code",
|
||||
categories: "Operating categories",
|
||||
categoriesHint: "Select one or more categories from the published category tree.",
|
||||
categoriesLoadFailed: "Unable to load the category tree.",
|
||||
contactInfo: "Contact details",
|
||||
contactName: "Contact name",
|
||||
contactPhone: "Contact phone",
|
||||
contactEmail: "Contact email",
|
||||
contactAddress: "Contact address (optional)",
|
||||
contactNamePlaceholder: "Who should we contact?",
|
||||
contactPhonePlaceholder: "Phone number with area code",
|
||||
contactEmailPlaceholder: "you{'@'}example.com",
|
||||
contactAddressPlaceholder: "Street, city, region",
|
||||
qualification: "Qualification materials",
|
||||
qualificationHint: "Provide public http(s) URLs only; this MVP has no file upload.",
|
||||
identityDocumentUrl: "Identity document URL",
|
||||
identityDocumentUrlPlaceholder: "https://example.com/id.jpg",
|
||||
businessLicenseUrl: "Business license URL",
|
||||
businessLicenseUrlPlaceholder: "https://example.com/license.jpg",
|
||||
extraMaterials: "Extra material URLs (optional)",
|
||||
extraMaterialPlaceholder: "https://example.com/extra.jpg",
|
||||
extraMaterialAdd: "Add material URL",
|
||||
extraMaterialRemove: "Remove",
|
||||
previous: "Back",
|
||||
next: "Next",
|
||||
submit: "Submit application",
|
||||
submitting: "Submitting…",
|
||||
signInToSubmit: "Sign in or create an account to submit — your answers are kept.",
|
||||
restoredDraft: "Welcome back. Your saved answers were restored; review them and submit.",
|
||||
successTitle: "Application submitted",
|
||||
successBody:
|
||||
"Your merchant application is now pending review. You can track its state at any time.",
|
||||
viewStatus: "View application status",
|
||||
conflict:
|
||||
"You already have a pending or approved merchant application. Track it on the status page instead of submitting another.",
|
||||
invalid: "Please check the highlighted fields and try again.",
|
||||
submitFailed: "Unable to submit the application. Please try again.",
|
||||
errorRequired: "This field is required.",
|
||||
errorEmail: "Enter a valid email address.",
|
||||
errorUrl: "Enter a valid http(s) URL.",
|
||||
errorCategories: "Select at least one operating category.",
|
||||
statusTitle: "Merchant application status",
|
||||
statusSubtitle: "The latest merchant application submitted by your account.",
|
||||
statusLoadFailed: "Unable to load your merchant applications.",
|
||||
noApplication: "You have not submitted a merchant application yet.",
|
||||
startApplication: "Start an application",
|
||||
status_pending: "Pending review",
|
||||
status_approved: "Approved",
|
||||
status_rejected: "Rejected",
|
||||
applicationId: "Application ID",
|
||||
entityKind: "Entity type",
|
||||
submittedAt: "Submitted",
|
||||
updatedAt: "Last updated",
|
||||
reviewedAt: "Reviewed",
|
||||
rejectionReason: "Rejection reason",
|
||||
createdShop: "Provisioned shop",
|
||||
reapply: "Apply again",
|
||||
historyTitle: "Application history",
|
||||
historyHint: "Earlier applications from this account.",
|
||||
backToJoin: "Back to the application form",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
merchant: {
|
||||
joinTitle: "商家入驻",
|
||||
joinSubtitle: "提交商家入驻申请,平台审核通过后将为您开通店铺和店主账号。",
|
||||
stepKind: "主体类型",
|
||||
stepEntity: "主体与经营类目",
|
||||
stepContact: "联系人与资质",
|
||||
kindPersonal: "个人",
|
||||
kindEnterprise: "企业",
|
||||
kindHint: "请选择店铺的经营主体类型,选择后需要填写的字段会随之变化。",
|
||||
entityInfo: "主体信息",
|
||||
realName: "真实姓名",
|
||||
realNamePlaceholder: "证件上的姓名",
|
||||
companyName: "企业名称",
|
||||
companyNamePlaceholder: "营业执照上的企业名称",
|
||||
businessLicenseNo: "营业执照号",
|
||||
businessLicenseNoPlaceholder: "统一社会信用代码",
|
||||
categories: "经营类目",
|
||||
categoriesHint: "请从已发布的类目树中选择一个或多个类目。",
|
||||
categoriesLoadFailed: "类目加载失败。",
|
||||
contactInfo: "联系信息",
|
||||
contactName: "联系人姓名",
|
||||
contactPhone: "联系电话",
|
||||
contactEmail: "联系邮箱",
|
||||
contactAddress: "联系地址(选填)",
|
||||
contactNamePlaceholder: "请填写联系人姓名",
|
||||
contactPhonePlaceholder: "请填写含区号的电话号码",
|
||||
contactEmailPlaceholder: "you{'@'}example.com",
|
||||
contactAddressPlaceholder: "省/市/区详细地址",
|
||||
qualification: "资质材料",
|
||||
qualificationHint: "仅支持填写公开的 http(s) 链接,本版本不提供文件上传。",
|
||||
identityDocumentUrl: "身份证件链接",
|
||||
identityDocumentUrlPlaceholder: "https://example.com/id.jpg",
|
||||
businessLicenseUrl: "营业执照链接",
|
||||
businessLicenseUrlPlaceholder: "https://example.com/license.jpg",
|
||||
extraMaterials: "其他材料链接(选填)",
|
||||
extraMaterialPlaceholder: "https://example.com/extra.jpg",
|
||||
extraMaterialAdd: "添加材料链接",
|
||||
extraMaterialRemove: "删除",
|
||||
previous: "上一步",
|
||||
next: "下一步",
|
||||
submit: "提交申请",
|
||||
submitting: "提交中…",
|
||||
signInToSubmit: "请先登录或注册后提交,已填写的内容会为您保留。",
|
||||
restoredDraft: "欢迎回来,已恢复您填写的申请内容,请确认后提交。",
|
||||
successTitle: "申请已提交",
|
||||
successBody: "您的入驻申请已提交,正在等待平台审核。您可以随时查看审核状态。",
|
||||
viewStatus: "查看申请状态",
|
||||
conflict: "您已有一条待审核或已通过的入驻申请,请前往状态页查看,无需重复提交。",
|
||||
invalid: "请检查标注的字段后重试。",
|
||||
submitFailed: "申请提交失败,请稍后重试。",
|
||||
errorRequired: "该项为必填。",
|
||||
errorEmail: "请填写有效的邮箱地址。",
|
||||
errorUrl: "请填写有效的 http(s) 链接。",
|
||||
errorCategories: "请至少选择一个经营类目。",
|
||||
statusTitle: "入驻申请状态",
|
||||
statusSubtitle: "您账号下最新提交的入驻申请。",
|
||||
statusLoadFailed: "入驻申请加载失败。",
|
||||
noApplication: "您还没有提交过入驻申请。",
|
||||
startApplication: "开始申请",
|
||||
status_pending: "待审核",
|
||||
status_approved: "已通过",
|
||||
status_rejected: "已拒绝",
|
||||
applicationId: "申请编号",
|
||||
entityKind: "主体类型",
|
||||
submittedAt: "提交时间",
|
||||
updatedAt: "更新时间",
|
||||
reviewedAt: "审核时间",
|
||||
rejectionReason: "拒绝原因",
|
||||
createdShop: "已开通店铺",
|
||||
reapply: "重新申请",
|
||||
historyTitle: "申请记录",
|
||||
historyHint: "该账号更早的申请记录。",
|
||||
backToJoin: "返回入驻申请",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// Buyer message center: list, filters, read/delete actions and the unread badge.
|
||||
export default {
|
||||
en: {
|
||||
messaging: {
|
||||
title: "Messages",
|
||||
loadFailed: "Unable to load your messages.",
|
||||
listFailed: "Unable to load messages.",
|
||||
empty: "No messages yet.",
|
||||
emptyUnread: "No unread messages.",
|
||||
unreadOnly: "Unread only",
|
||||
unreadBadge: "{count} unread messages",
|
||||
markAllRead: "Mark all as read",
|
||||
markAllSuccess: "Marked {count} message(s) as read.",
|
||||
markAllFailed: "Unable to mark all messages as read.",
|
||||
markRead: "Mark as read",
|
||||
markReadFailed: "Unable to mark the message as read.",
|
||||
open: "Open",
|
||||
close: "Close",
|
||||
delete: "Delete",
|
||||
deleteConfirm: "Delete this message?",
|
||||
deleteFailed: "Unable to delete the message.",
|
||||
createdAt: "Time",
|
||||
kind_order_paid: "Payment received",
|
||||
kind_order_shipped: "Order shipped",
|
||||
kind_refund_completed: "Refund completed",
|
||||
statusUnread: "Unread",
|
||||
statusRead: "Read",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
messaging: {
|
||||
title: "消息中心",
|
||||
loadFailed: "消息加载失败。",
|
||||
listFailed: "消息列表加载失败。",
|
||||
empty: "暂无消息。",
|
||||
emptyUnread: "暂无未读消息。",
|
||||
unreadOnly: "仅看未读",
|
||||
unreadBadge: "{count} 条未读消息",
|
||||
markAllRead: "全部标记为已读",
|
||||
markAllSuccess: "已将 {count} 条消息标记为已读。",
|
||||
markAllFailed: "全部标记已读失败。",
|
||||
markRead: "标记已读",
|
||||
markReadFailed: "标记已读失败。",
|
||||
open: "展开",
|
||||
close: "收起",
|
||||
delete: "删除",
|
||||
deleteConfirm: "确定删除这条消息吗?",
|
||||
deleteFailed: "删除消息失败。",
|
||||
createdAt: "时间",
|
||||
kind_order_paid: "付款成功",
|
||||
kind_order_shipped: "订单已发货",
|
||||
kind_refund_completed: "退款完成",
|
||||
statusUnread: "未读",
|
||||
statusRead: "已读",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -34,6 +34,7 @@ export default {
|
||||
v4: "448 service centers nationwide",
|
||||
copyright: "VMall Demo Mall — all content is fixed mock data.",
|
||||
icp: "Mock ICP 2026-0001",
|
||||
sellerJoin: "Become a Seller",
|
||||
},
|
||||
backToTop: "Top",
|
||||
},
|
||||
@@ -72,6 +73,7 @@ export default {
|
||||
v4: "448家维修网点 全国联保",
|
||||
copyright: "VMall 演示商城 —— 所有内容均为固定 mock 数据。",
|
||||
icp: "模拟备案号 2026-0001",
|
||||
sellerJoin: "成为商家",
|
||||
},
|
||||
backToTop: "顶部",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
export default {
|
||||
en: {
|
||||
wallet: {
|
||||
title: "My wallet",
|
||||
available: "Available balance",
|
||||
frozen: "Frozen funds",
|
||||
loadFailed: "Unable to load your wallet.",
|
||||
entriesTitle: "Fund entries",
|
||||
entriesEmpty: "No fund entries yet.",
|
||||
entriesFailed: "Unable to load fund entries.",
|
||||
entryDate: "Date",
|
||||
entryAccount: "Account",
|
||||
entryReason: "Reason",
|
||||
entryDelta: "Change",
|
||||
entryBalance: "Balance after",
|
||||
accountAvailable: "Available",
|
||||
accountFrozen: "Frozen",
|
||||
rechargeTitle: "Recharge",
|
||||
rechargeDemoBadge: "Simulated · demo",
|
||||
rechargeDemoNote:
|
||||
"This is a demo recharge: no payment provider is contacted and no real money moves.",
|
||||
rechargeAmount: "Recharge amount ({currency})",
|
||||
rechargeAmountHint:
|
||||
"Enter a major-unit amount in {currency}; it is credited to your available balance immediately.",
|
||||
rechargeSubmit: "Simulate recharge",
|
||||
rechargeSuccess: "Simulated recharge credited: {amount}.",
|
||||
rechargeFailed: "Recharge failed. Please try again.",
|
||||
rechargeConflict:
|
||||
"The recharge conflicted with the current account state, so it was not applied. Balances have been refreshed.",
|
||||
withdrawTitle: "Withdraw funds",
|
||||
withdrawHint:
|
||||
"A withdrawal leaves your available balance immediately and is held as frozen funds until the platform reviews the request.",
|
||||
withdrawAmount: "Withdrawal amount ({currency})",
|
||||
withdrawAmountHint: "It cannot exceed the available balance.",
|
||||
withdrawMethod: "Payout method",
|
||||
withdrawMethodBank: "Bank transfer",
|
||||
withdrawMethodAlipay: "Alipay",
|
||||
withdrawMethodWechat: "WeChat Pay",
|
||||
withdrawAccount: "Payout account",
|
||||
withdrawAccountHint: "Bank account number or payment handle",
|
||||
withdrawHolder: "Account holder (optional)",
|
||||
withdrawSubmit: "Submit withdrawal request",
|
||||
withdrawSuccess: "Withdrawal request submitted; the amount is now frozen and pending review.",
|
||||
withdrawConflict:
|
||||
"Insufficient available balance: a withdrawal freezes the full amount, so this request was rejected as a conflict. The balances below show the current server state.",
|
||||
withdrawFailed: "Unable to submit the withdrawal request.",
|
||||
validationRequired: "Enter a positive amount, a payout method and a payout account.",
|
||||
historyTitle: "Withdrawal requests",
|
||||
historyEmpty: "No withdrawal requests yet.",
|
||||
historyCreatedAt: "Requested",
|
||||
historyAmount: "Amount",
|
||||
historyMethod: "Method",
|
||||
historyAccount: "Payout account",
|
||||
historyStatus: "Status",
|
||||
historyNote: "Review note",
|
||||
historyReviewedAt: "Reviewed",
|
||||
status_pending: "Pending review",
|
||||
status_approved: "Approved",
|
||||
status_rejected: "Rejected",
|
||||
reason_wallet_recharge: "Simulated recharge",
|
||||
reason_wallet_withdrawal_freeze: "Withdrawal held",
|
||||
reason_wallet_withdrawal_approved: "Withdrawal paid out",
|
||||
reason_wallet_withdrawal_rejected: "Withdrawal returned",
|
||||
reason_order_payment: "Order payment",
|
||||
reason_aftersale_refund: "After-sale refund",
|
||||
reason_settlement_payout: "Settlement payout",
|
||||
reason_opening_balance: "Opening balance",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
wallet: {
|
||||
title: "我的钱包",
|
||||
available: "可用余额",
|
||||
frozen: "冻结资金",
|
||||
loadFailed: "钱包数据加载失败。",
|
||||
entriesTitle: "资金明细",
|
||||
entriesEmpty: "暂无资金明细。",
|
||||
entriesFailed: "资金明细加载失败。",
|
||||
entryDate: "时间",
|
||||
entryAccount: "账户",
|
||||
entryReason: "原因",
|
||||
entryDelta: "变动",
|
||||
entryBalance: "变动后余额",
|
||||
accountAvailable: "可用",
|
||||
accountFrozen: "冻结",
|
||||
rechargeTitle: "充值",
|
||||
rechargeDemoBadge: "模拟 · 演示",
|
||||
rechargeDemoNote: "这是模拟充值:不会接入任何支付渠道,也不会发生真实资金流转。",
|
||||
rechargeAmount: "充值金额({currency})",
|
||||
rechargeAmountHint: "请输入 {currency} 主单位金额,将立即计入可用余额。",
|
||||
rechargeSubmit: "模拟充值",
|
||||
rechargeSuccess: "模拟充值已到账:{amount}。",
|
||||
rechargeFailed: "充值失败,请重试。",
|
||||
rechargeConflict: "充值因账户状态冲突未生效,余额已重新加载。",
|
||||
withdrawTitle: "申请提现",
|
||||
withdrawHint: "提现会立即从可用余额中扣除并计为冻结资金,等待平台审核。",
|
||||
withdrawAmount: "提现金额({currency})",
|
||||
withdrawAmountHint: "不得超过可用余额。",
|
||||
withdrawMethod: "提现方式",
|
||||
withdrawMethodBank: "银行转账",
|
||||
withdrawMethodAlipay: "支付宝",
|
||||
withdrawMethodWechat: "微信支付",
|
||||
withdrawAccount: "收款账户",
|
||||
withdrawAccountHint: "银行卡号或收款账号",
|
||||
withdrawHolder: "开户人(选填)",
|
||||
withdrawSubmit: "提交提现申请",
|
||||
withdrawSuccess: "提现申请已提交,金额已冻结并等待审核。",
|
||||
withdrawConflict:
|
||||
"可用余额不足:提现会冻结全额资金,因此本次申请被拒绝(冲突)。下方余额为服务器当前状态。",
|
||||
withdrawFailed: "提现申请提交失败。",
|
||||
validationRequired: "请填写正确的金额、提现方式与收款账户。",
|
||||
historyTitle: "提现记录",
|
||||
historyEmpty: "暂无提现申请。",
|
||||
historyCreatedAt: "申请时间",
|
||||
historyAmount: "金额",
|
||||
historyMethod: "方式",
|
||||
historyAccount: "收款账户",
|
||||
historyStatus: "状态",
|
||||
historyNote: "审核备注",
|
||||
historyReviewedAt: "审核时间",
|
||||
status_pending: "待审核",
|
||||
status_approved: "已通过",
|
||||
status_rejected: "已拒绝",
|
||||
reason_wallet_recharge: "模拟充值",
|
||||
reason_wallet_withdrawal_freeze: "提现冻结",
|
||||
reason_wallet_withdrawal_approved: "提现已打款",
|
||||
reason_wallet_withdrawal_rejected: "提现已退回",
|
||||
reason_order_payment: "订单支付",
|
||||
reason_aftersale_refund: "售后退款",
|
||||
reason_settlement_payout: "结算打款",
|
||||
reason_opening_balance: "期初余额",
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user