用户 2026-09-30 两句话定了范围,本计划是那两句话的展开: ①「链接仅做到打开checkout完成输入金额并然后调用真实主扫api并拉起响应中的支付链接就行了, 但是checkout要做的漂亮,不含其他功能」 ②「checkout链接获取的按钮可以做到系统页面上,顺便封装成二维码,链接和二维码都可以拿走直接用」
Goal: 给每个乐收赢商户一条永久可复用的 https 收款链接;顾客打开它 → 输金额 → 确认 →
后端调真实主扫(/v3/qrpay)拿 code_url → 浏览器直接跳过去拉起支付宝/微信。
Architecture:
pay_lsy_checkout_link(租户+商户+token,token 全局唯一)。/#/checkout/:token,只做输入金额→跳转,不做轮询/结果页。Tech Stack: Spring Boot 3 + MyBatis-Plus(本模块既有约定)、Vue3 + Element Plus、qrcode npm(已在依赖里)。
Spec: 无独立 spec;依据是用户这两句话 + 本仓既有裁定(下「不可省的约束」逐条都指得出出处)。
TenantApiKeyAuthFilter 那套 HMAC 只能在服务端)。UNIQUE 索引(通则「甲·预防」,
见 TenantInnerInterceptor.CONDITIONAL_TABLES 上方那段)。out_trade_no 的唯一性是 (tenant_id, out_trade_no),不是全局 ⇒ 服务端生成
单号必须是雪花(全局唯一),不能是时间戳自增那种会跨租户撞的形态。pay_lsy_order.mch_no NOT NULL 且无默认值 ⇒ insert 必须在装配 mch_no 之后。PAYING(createQrOrder 里的既有判据,不改)。notify_url 有意留空(同收银台那条腿):上游回调走平台级 leshouying.notify-url,
与订单这一列无关;下游通知按 resolveUrl 回退到 open_conf。
★ 这条不能靠「照抄开放 API」得到 —— 那会把订单快照填成我们自己的回调入口 = 自环。LsyAmountFloor.MIN(1 元,我方业务线),文案同源。mch_no 属上游身份,本仓既有裁定不给租户看 ⇒ 链接里只有不透明 token。git add <显式路径>;application.yml 永不 add、永不读。后端(新建)
java/src/main/resources/db/lsy-checkout-link.sql —— 建表(只写不执行,交用户).../leshouying/entity/LsyCheckoutLinkEntity.java.../leshouying/mapper/LsyCheckoutLinkMapper.java.../leshouying/service/LsyCheckoutLinkService.java.../leshouying/controller/LeshouyingCheckoutController.java —— 免登录两个端点.../leshouying/dto/LsyCheckoutPayDTO.java / LsyCheckoutInfoVO.java / LsyCheckoutOrderVO.java后端(改)
core/security/SecurityConfig.java —— 白名单加 /payment/checkout/**core/tenant/TenantInnerInterceptor.java —— CONDITIONAL_TABLES 加本表(含 token 全局唯一那条理由).../controller/LeshouyingMerchantController.java —— 管理端「取收款链接」端点.../controller/open/LeshouyingOpenController.java —— 开放 API「取收款链接」端点core/security/TenantApiKeyAuthFilter.java —— OPENAPI_ACCOUNT_PATHS 补第 8 条前端(新建)
src/api/module_payment/checkout.ts —— 独立 axios 实例(绕开管理端拦截器)src/views/checkout/index.vue —— 免登录顾客页(漂亮、移动优先)src/views/module_payment/collection/merchant/CheckoutLinkDialog.vue —— 链接+二维码弹窗前端(改)
src/router/index.ts(静态路由加一条,无 Layout)、src/plugins/permission.ts(两分支之前放行)src/api/module_payment/collection.ts(管理端取链接)src/views/module_payment/collection/merchant/index.vue(操作列加按钮)pay_lsy_checkout_link:id/tenant_id/mch_no/token/merchant_name/status/created_time/updated_time;
uk_lsy_checkout_token (token) 全局唯一 + uk_lsy_checkout_tenant_mch (tenant_id, mch_no) 一商户一条。
风格照 V1.8__create_pay_lsy_tables.sql(BIGINT 主键、TIMESTAMP WITH TIME ZONE、COMMENT 齐全)。
LsyCheckoutLinkService:
issue(tenantId, mchNo, merchantName) —— 按 (tenant_id, mch_no) 找,没有就建(token = fastSimpleUUID);
重入返回同一条(链接永久复用);每次刷新显示名快照。resolve(String token) —— 按 token 定位,0 行 ⇒ 404「收款链接无效或已停用」。buildUrl(entity, request) —— 前缀取 leshouying.checkout-base-url(可空⇒按请求 Host 推导)。LeshouyingCheckoutController(/payment/checkout):
GET /{token} → { merchantName }POST /{token}/order {amount} → 建真主扫单(CS+雪花)→ 返回 { outTradeNo, codeUrl }tenant_id=0 ⇒ code_url 落库静默 0 行,见约束 2/3 的同类失效。)管理端 POST /payment/collection/merchant/{mchNo}/checkout-link(权限串复用 :merchant:list,
理由:租户本来就该拿到自己商户的收款链接;租户号取自会话,请求体里没有租户维度,不构成跨租户原语)
开放 API POST /payment/openapi/pay/checkout/link(走既有 requireMerchant 分流)。
顾客页:金额输入(元→分)→ 确认 → location.href = codeUrl;失败态一句话。
管理端:操作列「收款链接」→ 弹窗显示链接 + 二维码(qrcode 已在依赖里)+ 复制 + 下载 PNG。
collection-openapi.md 补开放 API 那一条;jar/dist 重建;交付说明写清用户侧要做的动作。