feat(system): 添加LWPAY和Tokenview支付配置功能
- 新增LWPAY支付配置接口和前端页面,支持商户号、签名密钥、回调地址等参数设置
- 新增Tokenview Webhook配置接口和前端页面,支持签名密钥和TRC20 USDT收款地址配置
- 添加LwPayConfigDto和TokenviewConfigDto数据传输对象
- 实现配置参数的数据库存储和读取功能
- 添加表单验证确保必填字段不为空
- 集成权限控制和操作日志记录功能
4 files added
2 files modified
| | |
| | | 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; |
| | |
| | | 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( |
| | |
| | | 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"); |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |