From 8afcd94ba0c6e587dc9cc79a0c1d922fe6fbca49 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Mon, 22 Jun 2026 17:45:17 +0800
Subject: [PATCH] feat(system): 添加LWPAY和Tokenview支付配置功能

---
 src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java     |   37 +++++++
 src/main/resources/templates/febs/views/modules/system/tokenviewConfig.html |   72 ++++++++++++++
 src/main/java/cc/mrbird/febs/mall/controller/ViewSystemController.java      |   29 +++++
 src/main/java/cc/mrbird/febs/mall/dto/TokenviewConfigDto.java               |   17 +++
 src/main/resources/templates/febs/views/modules/system/lwPayConfig.html     |   88 +++++++++++++++++
 src/main/java/cc/mrbird/febs/mall/dto/LwPayConfigDto.java                   |   21 ++++
 6 files changed, 264 insertions(+), 0 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java b/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java
index 3f9d310..6059db9 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java
@@ -12,6 +12,7 @@
 import cc.mrbird.febs.pay.model.HeaderDto;
 import cc.mrbird.febs.pay.service.WxFaPiaoService;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import lombok.RequiredArgsConstructor;
@@ -62,6 +63,42 @@
         return new FebsResponse().success();
     }
 
+    @PostMapping(value = "/lwPayConfig")
+    @ControllerEndpoint(operation = "保存LWPAY配置")
+    public FebsResponse lwPayConfig(LwPayConfigDto dto) {
+        if (StrUtil.isBlank(dto.getMemberId())) {
+            return new FebsResponse().fail().message("商户号不能为空");
+        }
+        if (StrUtil.isBlank(dto.getSecretKey())) {
+            return new FebsResponse().fail().message("签名密钥不能为空");
+        }
+        if (StrUtil.isBlank(dto.getNotifyUrl())) {
+            return new FebsResponse().fail().message("回调地址不能为空");
+        }
+        if (StrUtil.isBlank(dto.getReturnUrl())) {
+            return new FebsResponse().fail().message("跳转地址不能为空");
+        }
+        commonService.addDataDic("LWPAY_CONFIG", "MEMBER_ID", dto.getMemberId(), "LWPAY商户号", false);
+        commonService.addDataDic("LWPAY_CONFIG", "SECRET_KEY", dto.getSecretKey(), "LWPAY签名密钥", false);
+        commonService.addDataDic("LWPAY_CONFIG", "NOTIFY_URL", dto.getNotifyUrl(), "LWPAY异步回调地址", false);
+        commonService.addDataDic("LWPAY_CONFIG", "RETURN_URL", dto.getReturnUrl(), "LWPAY支付完成跳转地址", false);
+        return new FebsResponse().success().message("保存成功");
+    }
+
+    @PostMapping(value = "/tokenviewConfig")
+    @ControllerEndpoint(operation = "保存Tokenview配置")
+    public FebsResponse tokenviewConfig(TokenviewConfigDto dto) {
+        if (StrUtil.isBlank(dto.getWebhookSecret())) {
+            return new FebsResponse().fail().message("签名密钥不能为空");
+        }
+        if (StrUtil.isBlank(dto.getTrc20UsdtAddress())) {
+            return new FebsResponse().fail().message("收款地址不能为空");
+        }
+        commonService.addDataDic("TOKENVIEW_CONFIG", "WEBHOOK_SECRET", dto.getWebhookSecret(), "Tokenview Webhook签名密钥", false);
+        commonService.addDataDic("TOKENVIEW_CONFIG", "TRC20_USDT_ADDRESS", dto.getTrc20UsdtAddress(), "TRC20 USDT收款地址", false);
+        return new FebsResponse().success().message("保存成功");
+    }
+
     @PostMapping(value = "/agentAmountSetSetting")
     public FebsResponse agentAmountSetSetting(AdminAgentAmountDto adminAgentAmountDto) {
         DataDictionaryCustom dic = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/ViewSystemController.java b/src/main/java/cc/mrbird/febs/mall/controller/ViewSystemController.java
index 318b86c..481aff6 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/ViewSystemController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/ViewSystemController.java
@@ -129,4 +129,33 @@
     public String sender() {
         return FebsUtil.view("modules/system/sender");
     }
+
+    @GetMapping("lwPayConfig")
+    @RequiresPermissions("lwPayConfig:update")
+    public String lwPayConfig(Model model) {
+        LwPayConfigDto dto = new LwPayConfigDto();
+        // 逐条读取 LWPAY_CONFIG 下的配置项
+        DataDictionaryCustom memberId = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("LWPAY_CONFIG", "MEMBER_ID");
+        DataDictionaryCustom secretKey = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("LWPAY_CONFIG", "SECRET_KEY");
+        DataDictionaryCustom notifyUrl = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("LWPAY_CONFIG", "NOTIFY_URL");
+        DataDictionaryCustom returnUrl = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("LWPAY_CONFIG", "RETURN_URL");
+        if (memberId != null) dto.setMemberId(memberId.getValue());
+        if (secretKey != null) dto.setSecretKey(secretKey.getValue());
+        if (notifyUrl != null) dto.setNotifyUrl(notifyUrl.getValue());
+        if (returnUrl != null) dto.setReturnUrl(returnUrl.getValue());
+        model.addAttribute("lwPayConfig", dto);
+        return FebsUtil.view("modules/system/lwPayConfig");
+    }
+
+    @GetMapping("tokenviewConfig")
+    @RequiresPermissions("tokenviewConfig:update")
+    public String tokenviewConfig(Model model) {
+        TokenviewConfigDto dto = new TokenviewConfigDto();
+        DataDictionaryCustom secret = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("TOKENVIEW_CONFIG", "WEBHOOK_SECRET");
+        DataDictionaryCustom address = dataDictionaryCustomMapper.selectDicDataByTypeAndCode("TOKENVIEW_CONFIG", "TRC20_USDT_ADDRESS");
+        if (secret != null) dto.setWebhookSecret(secret.getValue());
+        if (address != null) dto.setTrc20UsdtAddress(address.getValue());
+        model.addAttribute("tokenviewConfig", dto);
+        return FebsUtil.view("modules/system/tokenviewConfig");
+    }
 }
diff --git a/src/main/java/cc/mrbird/febs/mall/dto/LwPayConfigDto.java b/src/main/java/cc/mrbird/febs/mall/dto/LwPayConfigDto.java
new file mode 100644
index 0000000..8f13fd6
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/dto/LwPayConfigDto.java
@@ -0,0 +1,21 @@
+package cc.mrbird.febs.mall.dto;
+
+import lombok.Data;
+
+/**
+ * LWPAY 支付配置 DTO
+ * 对应 data_dictionary_custom 表 type='LWPAY_CONFIG' 的多条记录
+ *
+ * @author auto-generated
+ */
+@Data
+public class LwPayConfigDto {
+    /** LWPAY 商户号 */
+    private String memberId;
+    /** LWPAY 签名密钥 */
+    private String secretKey;
+    /** 异步回调通知地址 */
+    private String notifyUrl;
+    /** 支付完成跳转地址 */
+    private String returnUrl;
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/dto/TokenviewConfigDto.java b/src/main/java/cc/mrbird/febs/mall/dto/TokenviewConfigDto.java
new file mode 100644
index 0000000..3b64149
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/dto/TokenviewConfigDto.java
@@ -0,0 +1,17 @@
+package cc.mrbird.febs.mall.dto;
+
+import lombok.Data;
+
+/**
+ * Tokenview Webhook 配置 DTO
+ * 对应 data_dictionary_custom 表 type='TOKENVIEW_CONFIG' 的多条记录
+ *
+ * @author auto-generated
+ */
+@Data
+public class TokenviewConfigDto {
+    /** Webhook HMAC-SHA256 签名密钥 */
+    private String webhookSecret;
+    /** TRC20 USDT 收款地址 */
+    private String trc20UsdtAddress;
+}
diff --git a/src/main/resources/templates/febs/views/modules/system/lwPayConfig.html b/src/main/resources/templates/febs/views/modules/system/lwPayConfig.html
new file mode 100644
index 0000000..a345ecf
--- /dev/null
+++ b/src/main/resources/templates/febs/views/modules/system/lwPayConfig.html
@@ -0,0 +1,88 @@
+<div class="layui-fluid layui-anim febs-anim" id="lwpay-config" lay-title="LWPAY支付设置">
+    <div class="layui-row layui-col-space8 febs-container">
+        <form class="layui-form" action="" lay-filter="lwpay-config-form">
+            <div class="layui-card">
+                <div class="layui-card-header">LWPAY 支付参数配置</div>
+                <div class="layui-card-body">
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">商户号:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="memberId" data-th-id="${lwPayConfig.memberId}"
+                                   lay-verify="required" autocomplete="off" class="layui-input" placeholder="LWPAY 商户号(MEMBER_ID)">
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">签名密钥:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="secretKey" data-th-id="${lwPayConfig.secretKey}"
+                                   lay-verify="required" autocomplete="off" class="layui-input" placeholder="LWPAY 签名密钥(SECRET_KEY)">
+                        </div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">回调地址:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="notifyUrl" data-th-id="${lwPayConfig.notifyUrl}"
+                                   lay-verify="required|url" autocomplete="off" class="layui-input" placeholder="异步回调通知地址(NOTIFY_URL)">
+                        </div>
+                        <div class="layui-word-aux" style="margin-left: 150px;">LWPAY 支付成功后异步通知的地址,必须以 http(s):// 开头</div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">跳转地址:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="returnUrl" data-th-id="${lwPayConfig.returnUrl}"
+                                   lay-verify="required|url" autocomplete="off" class="layui-input" placeholder="支付完成页面跳转地址(RETURN_URL)">
+                        </div>
+                        <div class="layui-word-aux" style="margin-left: 150px;">用户支付完成后跳转的页面地址,必须以 http(s):// 开头</div>
+                    </div>
+                </div>
+
+                <div class="layui-card-footer">
+                    <button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="lwpay-config-form-submit" id="submit">保存</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+<style>
+    .layui-form-label {
+        width: 120px;
+    }
+
+    .layui-form-item .layui-input-block {
+        margin-left: 150px;
+    }
+
+    .layui-table-form .layui-form-item {
+        margin-bottom: 20px !important;
+    }
+</style>
+<script data-th-inline="javascript" type="text/javascript">
+    layui.use(['dropdown', 'jquery', 'validate', 'febs', 'form'], function () {
+        var $ = layui.jquery,
+            febs = layui.febs,
+            form = layui.form,
+            lwPayConfig = [[${lwPayConfig}]],
+            validate = layui.validate,
+            $view = $('#lwpay-config');
+
+        form.verify(validate);
+
+        if (lwPayConfig) {
+            form.val("lwpay-config-form", {
+                "memberId": lwPayConfig.memberId,
+                "secretKey": lwPayConfig.secretKey,
+                "notifyUrl": lwPayConfig.notifyUrl,
+                "returnUrl": lwPayConfig.returnUrl
+            });
+        }
+
+        form.render();
+
+        form.on('submit(lwpay-config-form-submit)', function (data) {
+            febs.post(ctx + 'admin/system/lwPayConfig', data.field, function (res) {
+                febs.alert.success('保存成功');
+            });
+            return false;
+        });
+    });
+</script>
diff --git a/src/main/resources/templates/febs/views/modules/system/tokenviewConfig.html b/src/main/resources/templates/febs/views/modules/system/tokenviewConfig.html
new file mode 100644
index 0000000..d68a7aa
--- /dev/null
+++ b/src/main/resources/templates/febs/views/modules/system/tokenviewConfig.html
@@ -0,0 +1,72 @@
+<div class="layui-fluid layui-anim febs-anim" id="tokenview-config" lay-title="Tokenview配置">
+    <div class="layui-row layui-col-space8 febs-container">
+        <form class="layui-form" action="" lay-filter="tokenview-config-form">
+            <div class="layui-card">
+                <div class="layui-card-header">Tokenview Webhook 配置</div>
+                <div class="layui-card-body">
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">签名密钥:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="webhookSecret" data-th-id="${tokenviewConfig.webhookSecret}"
+                                   lay-verify="required" autocomplete="off" class="layui-input" placeholder="Webhook HMAC-SHA256 签名密钥">
+                        </div>
+                        <div class="layui-word-aux" style="margin-left: 150px;">用于验证 Tokenview 推送签名的密钥,需与 Tokenview 后台配置的 Secret Key 一致</div>
+                    </div>
+                    <div class="layui-form-item">
+                        <label class="layui-form-label required">收款地址:</label>
+                        <div class="layui-input-block">
+                            <input type="text" name="trc20UsdtAddress" data-th-id="${tokenviewConfig.trc20UsdtAddress}"
+                                   lay-verify="required" autocomplete="off" class="layui-input" placeholder="TRC20 USDT 收款地址">
+                        </div>
+                        <div class="layui-word-aux" style="margin-left: 150px;">TRC20 链上的 USDT 收款钱包地址</div>
+                    </div>
+                </div>
+
+                <div class="layui-card-footer">
+                    <button class="layui-btn layui-btn-normal" lay-submit="" lay-filter="tokenview-config-form-submit" id="submit">保存</button>
+                </div>
+            </div>
+        </form>
+    </div>
+</div>
+<style>
+    .layui-form-label {
+        width: 120px;
+    }
+
+    .layui-form-item .layui-input-block {
+        margin-left: 150px;
+    }
+
+    .layui-table-form .layui-form-item {
+        margin-bottom: 20px !important;
+    }
+</style>
+<script data-th-inline="javascript" type="text/javascript">
+    layui.use(['dropdown', 'jquery', 'validate', 'febs', 'form'], function () {
+        var $ = layui.jquery,
+            febs = layui.febs,
+            form = layui.form,
+            tokenviewConfig = [[${tokenviewConfig}]],
+            validate = layui.validate,
+            $view = $('#tokenview-config');
+
+        form.verify(validate);
+
+        if (tokenviewConfig) {
+            form.val("tokenview-config-form", {
+                "webhookSecret": tokenviewConfig.webhookSecret,
+                "trc20UsdtAddress": tokenviewConfig.trc20UsdtAddress
+            });
+        }
+
+        form.render();
+
+        form.on('submit(tokenview-config-form-submit)', function (data) {
+            febs.post(ctx + 'admin/system/tokenviewConfig', data.field, function (res) {
+                febs.alert.success('保存成功');
+            });
+            return false;
+        });
+    });
+</script>

--
Gitblit v1.9.1