From 0bd44afe3417454c5247c10b70897331e586536b Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 03 Jul 2026 11:25:45 +0800
Subject: [PATCH] feat(payment): 集成BSPAY巴西PIX支付功能

---
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java |  316 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 316 insertions(+), 0 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
index 2417866..130e66d 100644
--- a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/ApiMallOrderController.java
@@ -3,12 +3,22 @@
 import cc.mrbird.febs.common.annotation.Limit;
 import cc.mrbird.febs.common.entity.FebsResponse;
 import cc.mrbird.febs.common.entity.LimitType;
+import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
+import cc.mrbird.febs.common.utils.MallUtils;
+import cc.mrbird.febs.common.utils.ValidateEntityUtils;
+import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
 import cc.mrbird.febs.mall.dto.*;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
 import cc.mrbird.febs.mall.service.IApiMallOrderInfoService;
+import cc.mrbird.febs.mall.service.IMallCountryDeliveryService;
 import cc.mrbird.febs.mall.vo.ApiOrderPayVo;
 import cc.mrbird.febs.mall.vo.OrderDetailVo;
 import cc.mrbird.febs.mall.vo.OrderListVo;
+import cc.mrbird.febs.pay.service.BsPayService;
 import cc.mrbird.febs.pay.service.IXcxPayService;
+import cc.mrbird.febs.pay.service.LwPayService;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
@@ -18,6 +28,8 @@
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletRequest;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -35,6 +47,9 @@
 
     private final IApiMallOrderInfoService mallOrderInfoService;
     private final IXcxPayService iXcxPayService;
+    private final LwPayService lwPayService;
+    private final BsPayService bsPayService;
+    private final IMallCountryDeliveryService countryDeliveryService;
 
     @ApiOperation(value = "创建订单--验证是否允许创建", notes = "创建订单--验证是否允许创建")
     @PostMapping(value = "/createOrderVerify")
@@ -164,4 +179,305 @@
         return new FebsResponse().success().data(iXcxPayService.getTemplateId());
     }
 
+    // ==================== XT 支付 ====================
+
+    /**
+     * 创建订单并通过 XT 支付
+     * <p>
+     * 流程:创建订单 → 通过 XT 支付链接
+     * <p>
+     */
+    @ApiOperation(value = "创建订单-XT支付", notes = "创建订单并返回XT支付")
+    @PostMapping(value = "/createOrderByXtPay")
+    @Limit(key = "createOrderByXtPay", period = 1, count = 1, name = "XT下单", prefix = "limit", limitType = LimitType.IP)
+    public FebsResponse createOrderByXtPay(@RequestBody @Validated XtPayCreateOrderDto lwPayDto) {
+        // 1. 创建订单
+        AddOrderDto addOrderDto = lwPayDto.getOrder();
+        Long orderId = mallOrderInfoService.createOrder(addOrderDto);
+
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ){
+            return mallOrderInfoService.createOrderByXtPay();
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+    @ApiOperation(value = "XT支付", notes = "XT支付")
+    @PostMapping(value = "/payOrderByXtPay", produces = "application/json")
+    public FebsResponse payOrderByXtPay(@RequestBody @Validated ApiOrderPayDto payDto) {
+
+        Long orderId = payDto.getOrderId();
+        Integer payType = payDto.getPayType();
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderConstants.PAY_TYPE_XT == payType
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ){
+            return mallOrderInfoService.createOrderByXtPay();
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+    // ==================== LWPAY 支付 ====================
+
+    /**
+     * 创建订单并通过 LWPAY 支付
+     * <p>
+     * 流程:创建订单 → 调 LWPAY 代收接口 → 返回支付 URL
+     * <p>
+     * 参数说明:
+     * - bankCode: 银行编码,968=USDT-TRC20
+     * - network:  网络链,TRX/ETH/MATIC/BSC/SOL/ARBEVM(bankCode=968 时必填)
+     */
+    @ApiOperation(value = "创建订单-LWPAY支付", notes = "创建订单并返回LWPAY支付URL")
+    @PostMapping(value = "/createOrderByLwPay")
+    @Limit(key = "createOrderByLwPay", period = 1, count = 1, name = "LWPAY下单", prefix = "limit", limitType = LimitType.IP)
+    public FebsResponse createOrderByLwPay(@RequestBody @Validated LwPayCreateOrderDto lwPayDto) {
+        // 1. 创建订单
+        AddOrderDto addOrderDto = lwPayDto.getOrder();
+        Long orderId = mallOrderInfoService.createOrder(addOrderDto);
+
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ){
+
+            // 3. 调用 LWPAY 代收接口
+            try {
+                String payUrl = lwPayService.createPayment(
+                        order,
+                        lwPayDto.getBankCode(),
+                        lwPayDto.getNetwork()
+                );
+
+                Map<String, Object> result = new HashMap<>();
+                result.put("orderNo", order.getOrderNo());
+                result.put("amount", order.getAmount());
+                result.put("payUrl", payUrl);
+                return new FebsResponse().success().data(result);
+            } catch (Exception e) {
+                log.error("LWPAY 代收下单失败: orderId={}", orderId, e);
+                return new FebsResponse().fail().message("Payment channel exception: " + e.getMessage());
+            }
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+
+
+
+    @ApiOperation(value = "LWPAY支付", notes = "LWPAY支付")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success", response = ApiOrderPayVo.class)
+    })
+    @PostMapping(value = "/payOrderByLwPay", produces = "application/json")
+    public FebsResponse payOrderByLwPay(@RequestBody @Validated ApiOrderPayDto payDto) {
+
+        Long orderId = payDto.getOrderId();
+        Integer payType = payDto.getPayType();
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderConstants.PAY_TYPE_SYSTEM == payType
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ){
+            String orderNo = MallUtils.getOrderNum();
+            order.setOrderNo(orderNo);
+            mallOrderInfoService.getBaseMapper().update(
+                    null,
+                    Wrappers.lambdaUpdate(MallOrderInfo.class)
+                    .set(MallOrderInfo::getOrderNo, orderNo)
+                    .eq(MallOrderInfo::getId, orderId)
+            );
+            // 3. 调用 LWPAY 代收接口
+            try {
+                String payUrl = lwPayService.createPayment(
+                        order,
+                        "967",
+                        null
+                );
+
+                Map<String, Object> result = new HashMap<>();
+                result.put("orderNo", order.getOrderNo());
+                result.put("amount", order.getAmount());
+                result.put("payUrl", payUrl);
+                return new FebsResponse().success().data(result).message("success");
+            } catch (Exception e) {
+                log.error("LWPAY 代收下单失败: orderId={}", orderId, e);
+                return new FebsResponse().fail().message("Payment channel exception: " + e.getMessage());
+            }
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+    /**
+     * LWPAY 支付结果通知(服务端回调)
+     * <p>
+     * LWPAY 在支付完成后会 POST 通知到此地址。
+     * 需在 LWPAY 商户后台配置 pay_notifyurl 为此地址。
+     * <p>
+     * 注意:收到后必须响应 "OK",LWPAY 才会停止重试。
+     */
+    @ApiOperation(value = "LWPAY 支付回调", notes = "接收LWPAY异步通知")
+    @PostMapping(value = "/lwPayNotify")
+    public String lwPayNotify(HttpServletRequest request) {
+        // 解析 form 参数
+        Map<String, String> params = new HashMap<>();
+        Enumeration<String> paramNames = request.getParameterNames();
+        while (paramNames.hasMoreElements()) {
+            String name = paramNames.nextElement();
+            params.put(name, request.getParameter(name));
+        }
+
+        log.info("LWPAY 回调参数: {}", params);
+
+        boolean success = lwPayService.handleCallback(params);
+        return success ? "OK" : "FAIL";
+    }
+
+    /**
+     * LWPAY 支付结果页面跳转
+     * <p>
+     * 用户在 LWPAY 支付页面完成后跳回此地址。
+     * 需在 LWPAY 商户后台配置 pay_callbackurl 为此地址。
+     */
+    @ApiOperation(value = "LWPAY 支付跳转", notes = "LWPAY支付完成后的页面跳转")
+    @GetMapping(value = "/lwPayReturn")
+    public String lwPayReturn(HttpServletRequest request) {
+        // 简单跳转到前端结果页
+        String orderId = request.getParameter("orderid");
+        String returncode = request.getParameter("returncode");
+        log.info("LWPAY 页面跳转: orderNo={}, returncode={}", orderId, returncode);
+        return "redirect:/pages/payResult?orderNo=" + orderId + "&code=" + returncode;
+    }
+
+    // ==================== BSPAY (巴西PIX) 支付 ====================
+
+    /**
+     * 创建订单并通过 BSPAY (巴西PIX) 支付
+     * <p>
+     * 流程:创建订单 → 调 BSPAY 下单接口 → 返回支付 URL (mweb_url)
+     * <p>
+     * 交易类型:trade_type=201 (巴西PIX),货币:BRL
+     */
+    @ApiOperation(value = "创建订单-BSPAY巴西PIX支付", notes = "创建订单并返回BSPAY PIX支付URL")
+    @PostMapping(value = "/createOrderByBsPay")
+    @Limit(key = "createOrderByBsPay", period = 1, count = 1, name = "BSPAY下单", prefix = "limit", limitType = LimitType.IP)
+    public FebsResponse createOrderByBsPay(@RequestBody @Validated BsPayCreateOrderDto bsPayDto) {
+        // 1. 创建订单
+        AddOrderDto addOrderDto = bsPayDto.getOrder();
+        Long orderId = mallOrderInfoService.createOrder(addOrderDto);
+
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ) {
+            // 3. 调用 BSPAY 下单接口
+            try {
+                String payUrl = bsPayService.createPayment(order);
+
+                Map<String, Object> result = new HashMap<>();
+                result.put("orderNo", order.getOrderNo());
+                result.put("amount", order.getAmount());
+                result.put("payUrl", payUrl);
+                return new FebsResponse().success().data(result);
+            } catch (Exception e) {
+                log.error("BSPAY 下单失败: orderId={}", orderId, e);
+                return new FebsResponse().fail().message("Payment channel exception: " + e.getMessage());
+            }
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+    @ApiOperation(value = "BSPAY巴西PIX支付", notes = "BSPAY巴西PIX支付")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success", response = ApiOrderPayVo.class)
+    })
+    @PostMapping(value = "/payOrderByBsPay", produces = "application/json")
+    public FebsResponse payOrderByBsPay(@RequestBody @Validated ApiOrderPayDto payDto) {
+
+        Long orderId = payDto.getOrderId();
+        Integer payType = payDto.getPayType();
+        // 2. 获取订单详情
+        MallOrderInfo order = mallOrderInfoService.getById(orderId);
+        if (order != null
+                && OrderConstants.PAY_TYPE_BS == payType
+                && OrderStatusEnum.WAIT_PAY.getValue() == order.getStatus()
+        ) {
+            // 3. 调用 BSPAY 下单接口
+            try {
+                String payUrl = bsPayService.createPayment(order);
+
+                Map<String, Object> result = new HashMap<>();
+                result.put("orderNo", order.getOrderNo());
+                result.put("amount", order.getAmount());
+                result.put("payUrl", payUrl);
+                return new FebsResponse().success().data(result).message("success");
+            } catch (Exception e) {
+                log.error("BSPAY 下单失败: orderId={}", orderId, e);
+                return new FebsResponse().fail().message("Payment channel exception: " + e.getMessage());
+            }
+        }
+        return new FebsResponse().fail().message("Payment channel exception");
+    }
+
+    /**
+     * BSPAY 支付结果通知(服务端回调)
+     * <p>
+     * BSPAY 在支付完成后会 POST JSON 通知到此地址。
+     * 需在 BSPAY 下单时传 notify_url 为此地址。
+     * <p>
+     * 注意:收到后必须响应 "SUCCESS",BSPAY 才会停止重试(最多5次)。
+     */
+    @ApiOperation(value = "BSPAY 支付回调", notes = "接收BSPAY异步通知")
+    @PostMapping(value = "/bsPayNotify")
+    public String bsPayNotify(HttpServletRequest request) {
+        // 读取 JSON body
+        try {
+            java.io.BufferedReader reader = request.getReader();
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String body = sb.toString();
+            log.info("BSPAY 回调原始数据: {}", body);
+
+            if (StrUtil.isBlank(body)) {
+                log.error("BSPAY 回调数据为空");
+                return "FAIL";
+            }
+
+            // 解析 JSON 为 Map
+            Map<String, String> params = new HashMap<>();
+            com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSONObject.parseObject(body);
+            for (String key : json.keySet()) {
+                params.put(key, json.getString(key));
+            }
+
+            log.info("BSPAY 回调参数: {}", params);
+
+            boolean success = bsPayService.handleCallback(params);
+            return success ? "SUCCESS" : "FAIL";
+        } catch (Exception e) {
+            log.error("BSPAY 回调处理异常", e);
+            return "FAIL";
+        }
+    }
+
+    @ApiOperation(value = "根据国家编码查询运费", notes = "根据国家编码查询对应运费")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "success")
+    })
+    @GetMapping(value = "/getShippingFee")
+    public FebsResponse getShippingFee(@RequestParam String countryCode) {
+        return countryDeliveryService.getShippingFeeByCountryCode(countryCode);
+    }
+
 }

--
Gitblit v1.9.1