Переглянути джерело

feat(pay): 集成威富通支付接口及相关工具类

- 新增 AppletWFTOrderController 处理威富通支付订单创建请求
- AppLevelOrderForm 增加隐藏IP字段用于支付请求
- AppInvokeChargeForm 添加connectorInfoId字段
- AppletOrderController createOrder方法增加HttpServletRequest参数
- 充电订单逻辑中接入AppletHomeService计算预支付金额
- 添加Swiftpass支付相关工具类PayUtill、RSAUtil、SignUtil及MD5签名实现
- 配置文件application-prod.yml新增微信相关支付及威富通配置信息
- 删除CouponUsageService中多余依赖,简化代码依赖关系
wzq 2 днів тому
батько
коміт
036a88ef5b
30 змінених файлів з 1943 додано та 22 видалено
  1. 2 1
      src/main/java/com/zsElectric/boot/business/controller/applet/AppletOrderController.java
  2. 198 0
      src/main/java/com/zsElectric/boot/business/controller/applet/AppletWFTOrderController.java
  3. 1 1
      src/main/java/com/zsElectric/boot/business/mapper/UserOrderInfoMapper.java
  4. 1 1
      src/main/java/com/zsElectric/boot/business/model/entity/UserAccount.java
  5. 1 1
      src/main/java/com/zsElectric/boot/business/model/form/UserAccountForm.java
  6. 4 0
      src/main/java/com/zsElectric/boot/business/model/form/applet/AppInvokeChargeForm.java
  7. 3 0
      src/main/java/com/zsElectric/boot/business/model/form/applet/AppLevelOrderForm.java
  8. 1 1
      src/main/java/com/zsElectric/boot/business/model/vo/UserAccountVO.java
  9. 43 0
      src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTCloseOrderVO.java
  10. 52 0
      src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTOrderVO.java
  11. 59 0
      src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundQueryVO.java
  12. 46 0
      src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundRecordVO.java
  13. 64 0
      src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundVO.java
  14. 0 8
      src/main/java/com/zsElectric/boot/business/service/CouponUsageService.java
  15. 3 0
      src/main/java/com/zsElectric/boot/business/service/UserOrderInfoService.java
  16. 158 0
      src/main/java/com/zsElectric/boot/business/service/WFTOrderService.java
  17. 8 4
      src/main/java/com/zsElectric/boot/business/service/impl/ChargeOrderInfoServiceImpl.java
  18. 7 1
      src/main/java/com/zsElectric/boot/business/service/impl/UserOrderInfoServiceImpl.java
  19. 90 0
      src/main/java/com/zsElectric/boot/core/pay/WFT/WFTConstants.java
  20. 122 0
      src/main/java/com/zsElectric/boot/core/pay/WFT/WFTPayUtils.java
  21. 2 2
      src/main/java/com/zsElectric/boot/core/pay/WechatConstants.java
  22. 1 1
      src/main/java/com/zsElectric/boot/core/pay/WechatUrlConstants.java
  23. 123 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/config/SwiftpassConfig.java
  24. 70 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/MD5.java
  25. 339 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/PayUtill.java
  26. 93 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/RSAUtil.java
  27. 140 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/SignUtil.java
  28. 134 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/SignUtils.java
  29. 158 0
      src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/XmlUtils.java
  30. 20 1
      src/main/resources/application-prod.yml

+ 2 - 1
src/main/java/com/zsElectric/boot/business/controller/applet/AppletOrderController.java

@@ -12,6 +12,7 @@ import com.zsElectric.boot.business.service.UserOrderInfoService;
 import com.zsElectric.boot.common.annotation.Log;
 import com.zsElectric.boot.common.constant.SystemConstants;
 import com.zsElectric.boot.common.enums.LogModuleEnum;
+import com.zsElectric.boot.common.util.IPUtils;
 import com.zsElectric.boot.core.web.Result;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -67,7 +68,7 @@ public class AppletOrderController {
      */
     @Operation(summary = "创建订单")
     @PostMapping("/createOrder")
-    public Result<AppUserPayForm> createOrder(@RequestBody AppLevelOrderForm appLevelOrderForm) {
+    public Result<AppUserPayForm> createOrder(@RequestBody AppLevelOrderForm appLevelOrderForm, HttpServletRequest request) {
         AppUserPayForm payForm = userOrderInfoService.createOrder(appLevelOrderForm);
         return Result.success(payForm);
     }

+ 198 - 0
src/main/java/com/zsElectric/boot/business/controller/applet/AppletWFTOrderController.java

@@ -0,0 +1,198 @@
+package com.zsElectric.boot.business.controller.applet;
+
+import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
+import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
+import com.zsElectric.boot.business.service.WFTOrderService;
+import com.zsElectric.boot.common.annotation.Log;
+import com.zsElectric.boot.common.enums.LogModuleEnum;
+import com.zsElectric.boot.common.util.IPUtils;
+import com.zsElectric.boot.core.web.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 威富通订单控制器
+ * 
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Tag(name = "威富通订单相关接口")
+@Slf4j
+@RestController
+@RequestMapping("/applet/v1/wft/order")
+@RequiredArgsConstructor
+public class AppletWFTOrderController {
+
+    private final WFTOrderService wftOrderService;
+
+    /**
+     * 创建订单
+     * 
+     * @param appLevelOrderForm 支付请求表单
+     * @return 支付信息(用于前端拉起支付)
+     */
+    @Operation(summary = "创建订单")
+    @PostMapping("/createOrder")
+    @Log(value = "创建订单", module = LogModuleEnum.APP_ORDER)
+    public Result<AppUserPayForm> createOrder(@Valid @RequestBody AppLevelOrderForm appLevelOrderForm,HttpServletRequest request) {
+        String IP = IPUtils.getIpAddr(request);
+        appLevelOrderForm.setIp(IP);
+        AppUserPayForm payForm = wftOrderService.createOrder(appLevelOrderForm);
+        return Result.success(payForm);
+    }
+
+//    /**
+//     * 威富通支付通知回调
+//     *
+//     * 重要说明:
+//     * 1. 此接口接收威富通支付成功后的异步通知
+//     * 2. 必须返回纯字符串"success"或"fail"
+//     * 3. 如果返回"fail"或超过5秒未响应,威富通会重试通知
+//     * 4. 重试策略:0/15/15/30/180/1800/1800/1800/1800/3600秒
+//     * 5. 必须做好幂等性处理,避免重复处理
+//     *
+//     * @param request HTTP请求对象
+//     * @return "success" 或 "fail"
+//     */
+//    @Operation(summary = "威富通支付通知回调")
+//    @PostMapping("/notify")
+//    public String payNotify(HttpServletRequest request) {
+//        log.info("========== 威富通支付通知回调开始 ==========");
+//        try {
+//            String result = wftOrderService.handlePayNotify(request);
+//            log.info("========== 威富通支付通知回调结束,返回:{} ==========", result);
+//            return result;
+//        } catch (Exception e) {
+//            log.error("威富通支付通知回调异常", e);
+//            return "fail";
+//        }
+//    }
+//
+//    /**
+//     * 订单查询
+//     *
+//     * 用于主动查询订单支付状态
+//     * 建议使用场景:
+//     * 1. 用户支付后前端轮询查询
+//     * 2. 后台定时任务轮询未支付订单
+//     * 3. 支付通知未收到时的补偿查询
+//     *
+//     * @param outTradeNo 商户订单号
+//     * @return 订单状态信息
+//     */
+//    @Operation(summary = "订单查询")
+//    @GetMapping("/query/{outTradeNo}")
+//    public Result<WFTOrderVO> queryOrder(
+//            @Parameter(description = "商户订单号") @PathVariable String outTradeNo) {
+//
+//        WFTOrderVO result = wftOrderService.queryOrderVO(outTradeNo);
+//
+//        return Result.success(result);
+//    }
+//
+//    /**
+//     * 关闭订单
+//     *
+//     * 业务功能:
+//     * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付
+//     * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口
+//     *
+//     * 重要说明:
+//     * 1. result_code为0表示关单成功,此笔订单不能再发起支付
+//     * 2. result_code为非0或其它表示关单接口异常,可再次发起关单操作
+//     * 3. 已支付成功的订单无法关闭
+//     * 4. 关单接口具有幂等性,可重复调用
+//     *
+//     * @param outTradeNo 商户订单号
+//     * @return 关单结果
+//     */
+//    @Operation(summary = "关闭订单")
+//    @PostMapping("/close/{outTradeNo}")
+//    @Log(value = "关闭订单", module = LogModuleEnum.APP_ORDER)
+//    public Result<WFTCloseOrderVO> closeOrder(
+//            @Parameter(description = "商户订单号") @PathVariable String outTradeNo) {
+//
+//        WFTCloseOrderVO result = wftOrderService.closeOrderVO(outTradeNo);
+//
+//        return Result.success(result);
+//    }
+//
+//    /**
+//     * 申请退款
+//     *
+//     * 业务功能:
+//     * 商户针对某一个已经成功支付的订单发起退款,操作结果在同一会话中同步返回。
+//     *
+//     * 退款方式:
+//     * 目前只支持原路返回退款。退到银行卡则是非实时的,每个银行的处理速度不同,
+//     * 一般发起退款后1-3个工作日内到账。
+//     *
+//     * 重要说明:
+//     * 1. 一笔交易单可以多次退款,只要退款累计金额不超过交易单支付总额
+//     * 2. 退款申请单号唯一确定一次退款,商户必须保证退款申请单的唯一性
+//     * 3. 一笔退款失败后重新提交,请使用原商户退款单号,不要更换
+//     * 4. result_code为0表示退款申请接收成功,实际的退款结果根据退款查询接口查询
+//     * 5. 微信支付订单的部分退款次数不能超过50次
+//     *
+//     * @param form 退款请求表单
+//     * @return 退款结果
+//     */
+//    @Operation(summary = "申请退款")
+//    @PostMapping("/refund")
+//    @Log(value = "申请退款", module = LogModuleEnum.APP_ORDER)
+//    public Result<WFTRefundVO> refundOrder(@Valid @RequestBody WFTRefundForm form) {
+//
+//        WFTRefundVO result = wftOrderService.refundOrderVO(
+//                form.getOutTradeNo(),
+//                form.getTransactionId(),
+//                form.getOutRefundNo(),
+//                form.getTotalFee(),
+//                form.getRefundFee(),
+//                form.getOpUserId(),
+//                form.getAttach());
+//
+//        return Result.success(result);
+//    }
+//
+//    /**
+//     * 查询退款
+//     *
+//     * 业务功能:
+//     * 提交退款申请后,通过调用该接口查询退款状态。
+//     * 退款有一定延时,请在3个工作日后重新查询退款状态。
+//     *
+//     * 参数优先级:
+//     * refund_id > out_refund_no > transaction_id > out_trade_no
+//     *
+//     * 退款状态:
+//     * - SUCCESS:退款成功
+//     * - FAIL:退款失败
+//     * - PROCESSING:退款处理中
+//     * - CHANGE:转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,
+//     *          资金回流到商户的现金帐号,需要商户人工干预,通过线下或者平台转账的方式进行退款
+//     *
+//     * @param outTradeNo 商户订单号(与transactionId/outRefundNo/refundId四选一)
+//     * @param transactionId 平台订单号(与outTradeNo/outRefundNo/refundId四选一)
+//     * @param outRefundNo 商户退款单号(与outTradeNo/transactionId/refundId四选一)
+//     * @param refundId 平台退款单号(与outTradeNo/transactionId/outRefundNo四选一,优先级最高)
+//     * @return 退款查询结果
+//     */
+//    @Operation(summary = "查询退款")
+//    @GetMapping("/refund/query")
+//    public Result<WFTRefundQueryVO> queryRefund(
+//            @Parameter(description = "商户订单号(四选一)") @RequestParam(required = false) String outTradeNo,
+//            @Parameter(description = "平台订单号(四选一)") @RequestParam(required = false) String transactionId,
+//            @Parameter(description = "商户退款单号(四选一)") @RequestParam(required = false) String outRefundNo,
+//            @Parameter(description = "平台退款单号(四选一,优先级最高)") @RequestParam(required = false) String refundId) {
+//
+//        WFTRefundQueryVO result = wftOrderService.queryRefundVO(
+//                outTradeNo, transactionId, outRefundNo, refundId);
+//
+//        return Result.success(result);
+//    }
+}

+ 1 - 1
src/main/java/com/zsElectric/boot/business/mapper/UserOrderInfoMapper.java

@@ -1,5 +1,6 @@
 package com.zsElectric.boot.business.mapper;
 
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.zsElectric.boot.business.model.entity.UserOrderInfo;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -24,5 +25,4 @@ public interface UserOrderInfoMapper extends BaseMapper<UserOrderInfo> {
      * @return {@link Page<UserOrderInfoVO>} 用户支付订单信息分页列表
      */
     Page<UserOrderInfoVO> getUserOrderInfoPage(Page<UserOrderInfoVO> page, UserOrderInfoQuery queryParams);
-
 }

+ 1 - 1
src/main/java/com/zsElectric/boot/business/model/entity/UserAccount.java

@@ -36,7 +36,7 @@ public class UserAccount extends BaseEntity {
     /**
      * 积分
      */
-    private Integer integral;
+    private BigDecimal integral;
     /**
      * 创建人
      */

+ 1 - 1
src/main/java/com/zsElectric/boot/business/model/form/UserAccountForm.java

@@ -37,7 +37,7 @@ public class UserAccountForm implements Serializable {
     private String redeemBalance;
 
     @Schema(description = "积分")
-    private Integer integral;
+    private BigDecimal integral;
 
     @Schema(description = "创建时间")
     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")

+ 4 - 0
src/main/java/com/zsElectric/boot/business/model/form/applet/AppInvokeChargeForm.java

@@ -34,6 +34,10 @@ public class AppInvokeChargeForm implements Serializable {
     @NotBlank(message = "设备认证流水号不能为空")
     private String equipAuthSeq;
 
+    @Schema(description = "充电设备接口编码")
+    @NotBlank(message = "充电设备接口编码不能为空")
+    private Long connectorInfoId;
+
     @Schema(description = "充电设备接口编码")
     @NotBlank(message = "充电设备接口编码不能为空")
     private String connectorId;

+ 3 - 0
src/main/java/com/zsElectric/boot/business/model/form/applet/AppLevelOrderForm.java

@@ -19,4 +19,7 @@ public class AppLevelOrderForm implements Serializable {
     @Schema(description = "档位ID")
     @NotNull(message = "请选择充值档位")
     private Long levelId;
+
+    @Schema(description = "ip",hidden = true)
+    private String ip;
 }

+ 1 - 1
src/main/java/com/zsElectric/boot/business/model/vo/UserAccountVO.java

@@ -34,7 +34,7 @@ public class UserAccountVO implements Serializable {
     @Schema(description = "兑换余额")
     private String redeemBalance;
     @Schema(description = "积分")
-    private Integer integral;
+    private BigDecimal integral;
     @Schema(description = "创建时间")
     private LocalDateTime createTime;
     @Schema(description = "创建人")

+ 43 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTCloseOrderVO.java

@@ -0,0 +1,43 @@
+package com.zsElectric.boot.business.model.vo.applet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 威富通关闭订单响应VO
+ *
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Data
+@Schema(description = "威富通关闭订单响应")
+public class WFTCloseOrderVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "返回状态码(0表示成功)")
+    private String status;
+
+    @Schema(description = "业务结果(0表示关单成功)")
+    private String resultCode;
+
+    @Schema(description = "商户号")
+    private String mchId;
+
+    @Schema(description = "商户订单号")
+    private String outTradeNo;
+
+    @Schema(description = "错误代码")
+    private String errCode;
+
+    @Schema(description = "错误描述")
+    private String errMsg;
+
+    @Schema(description = "操作是否成功")
+    private Boolean success;
+
+    @Schema(description = "结果描述信息")
+    private String message;
+}

+ 52 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTOrderVO.java

@@ -0,0 +1,52 @@
+package com.zsElectric.boot.business.model.vo.applet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 威富通订单查询响应VO
+ *
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Data
+@Schema(description = "威富通订单查询响应")
+public class WFTOrderVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "返回状态码(0表示成功)")
+    private String status;
+
+    @Schema(description = "业务结果(0表示成功)")
+    private String resultCode;
+
+    @Schema(description = "支付结果(0表示支付成功)")
+    private String payResult;
+
+    @Schema(description = "平台订单号")
+    private String transactionId;
+
+    @Schema(description = "商户订单号")
+    private String outTradeNo;
+
+    @Schema(description = "总金额(单位:分)")
+    private String totalFee;
+
+    @Schema(description = "支付完成时间(格式:yyyyMMddHHmmss)")
+    private String timeEnd;
+
+    @Schema(description = "错误代码")
+    private String errCode;
+
+    @Schema(description = "错误描述")
+    private String errMsg;
+
+    @Schema(description = "操作是否成功")
+    private Boolean success;
+
+    @Schema(description = "结果描述信息")
+    private String message;
+}

+ 59 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundQueryVO.java

@@ -0,0 +1,59 @@
+package com.zsElectric.boot.business.model.vo.applet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 威富通查询退款响应VO
+ *
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Data
+@Schema(description = "威富通查询退款响应")
+public class WFTRefundQueryVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "返回状态码(0表示成功)")
+    private String status;
+
+    @Schema(description = "业务结果(0表示成功)")
+    private String resultCode;
+
+    @Schema(description = "商户号")
+    private String mchId;
+
+    @Schema(description = "交易类型")
+    private String tradeType;
+
+    @Schema(description = "第三方订单号")
+    private String outTransactionId;
+
+    @Schema(description = "平台订单号")
+    private String transactionId;
+
+    @Schema(description = "商户订单号")
+    private String outTradeNo;
+
+    @Schema(description = "退款笔数")
+    private Integer refundCount;
+
+    @Schema(description = "退款记录列表")
+    private List<WFTRefundRecordVO> refundList;
+
+    @Schema(description = "错误代码")
+    private String errCode;
+
+    @Schema(description = "错误描述")
+    private String errMsg;
+
+    @Schema(description = "操作是否成功")
+    private Boolean success;
+
+    @Schema(description = "结果描述信息")
+    private String message;
+}

+ 46 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundRecordVO.java

@@ -0,0 +1,46 @@
+package com.zsElectric.boot.business.model.vo.applet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 威富通退款记录VO
+ *
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Data
+@Schema(description = "威富通退款记录")
+public class WFTRefundRecordVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "第三方退款单号")
+    private String outRefundId;
+
+    @Schema(description = "商户退款单号")
+    private String outRefundNo;
+
+    @Schema(description = "平台退款单号")
+    private String refundId;
+
+    @Schema(description = "退款渠道(ORIGINAL-原路退款)")
+    private String refundChannel;
+
+    @Schema(description = "退款金额(单位:分)")
+    private String refundFee;
+
+    @Schema(description = "现金券退款金额(单位:分)")
+    private String couponRefundFee;
+
+    @Schema(description = "退款时间(格式:yyyyMMddHHmmss)")
+    private String refundTime;
+
+    @Schema(description = "退款状态(SUCCESS-成功/FAIL-失败/PROCESSING-处理中/CHANGE-转入代发)")
+    private String refundStatus;
+
+    @Schema(description = "退款状态信息")
+    private String refundStatusInfo;
+}

+ 64 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTRefundVO.java

@@ -0,0 +1,64 @@
+package com.zsElectric.boot.business.model.vo.applet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 威富通申请退款响应VO
+ *
+ * @author wzq
+ * @since 2025-12-29
+ */
+@Data
+@Schema(description = "威富通申请退款响应")
+public class WFTRefundVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "返回状态码(0表示成功)")
+    private String status;
+
+    @Schema(description = "业务结果(0表示退款申请接收成功)")
+    private String resultCode;
+
+    @Schema(description = "商户号")
+    private String mchId;
+
+    @Schema(description = "交易类型")
+    private String tradeType;
+
+    @Schema(description = "第三方订单号")
+    private String outTransactionId;
+
+    @Schema(description = "平台订单号")
+    private String transactionId;
+
+    @Schema(description = "商户订单号")
+    private String outTradeNo;
+
+    @Schema(description = "商户退款单号")
+    private String outRefundNo;
+
+    @Schema(description = "平台退款单号")
+    private String refundId;
+
+    @Schema(description = "退款渠道(ORIGINAL-原路退款)")
+    private String refundChannel;
+
+    @Schema(description = "退款金额(单位:分)")
+    private String refundFee;
+
+    @Schema(description = "错误代码")
+    private String errCode;
+
+    @Schema(description = "错误描述")
+    private String errMsg;
+
+    @Schema(description = "操作是否成功")
+    private Boolean success;
+
+    @Schema(description = "结果描述信息")
+    private String message;
+}

+ 0 - 8
src/main/java/com/zsElectric/boot/business/service/CouponUsageService.java

@@ -21,14 +21,6 @@ import java.time.LocalDateTime;
 public class CouponUsageService extends ServiceImpl<CouponMapper, Coupon> {
 
     private final CouponService couponService;
-    private final CouponTemplateService couponTemplateService;
-    private final CouponCalculationService couponCalculationService;
-
-
-
-
-
-
 
     /**
      * 过期优惠券处理

+ 3 - 0
src/main/java/com/zsElectric/boot/business/service/UserOrderInfoService.java

@@ -9,6 +9,7 @@ import com.zsElectric.boot.business.model.query.applet.AppUserOrderInfoQuery;
 import com.zsElectric.boot.business.model.vo.UserOrderInfoVO;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.zsElectric.boot.business.model.vo.applet.AppUserInfoVO;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
@@ -77,4 +78,6 @@ public interface UserOrderInfoService extends IService<UserOrderInfo> {
     Map<String, Object> refundCallback(HttpServletRequest request, HttpServletResponse response);
 
     IPage<UserOrderInfoVO> getTicketRecords(AppUserOrderInfoQuery queryParams);
+
+    AppUserInfoVO getAppletUserInfo(Long userId);
 }

+ 158 - 0
src/main/java/com/zsElectric/boot/business/service/WFTOrderService.java

@@ -0,0 +1,158 @@
+package com.zsElectric.boot.business.service;
+
+import com.zsElectric.boot.business.mapper.RechargeLevelMapper;
+import com.zsElectric.boot.business.mapper.UserInfoMapper;
+import com.zsElectric.boot.business.mapper.UserOrderInfoMapper;
+import com.zsElectric.boot.business.model.entity.RechargeLevel;
+import com.zsElectric.boot.business.model.entity.UserOrderInfo;
+import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
+import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
+import com.zsElectric.boot.core.pay.WFT.WFTConstants;
+import com.zsElectric.boot.core.pay.swiftpass.config.SwiftpassConfig;
+import com.zsElectric.boot.core.pay.swiftpass.util.PayUtill;
+import com.zsElectric.boot.security.util.SecurityUtils;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 威富通支付服务
+ * 用于处理威富通JSAPI支付接口
+ *
+ * @author zsElectric
+ * @since 2025-12-29
+ */
+@Slf4j
+@Service
+public class WFTOrderService {
+
+    @Resource
+    private UserOrderInfoMapper userOrderInfoMapper;
+
+    @Resource
+    private UserInfoMapper userInfoMapper;
+
+    @Resource
+    private RechargeLevelMapper rechargeLevelMapper;
+
+    @Resource
+    protected SwiftpassConfig swiftpassConfig;
+
+
+    /**
+     * 创建商户订单号
+     * 要求 32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
+     * 组成 两位前缀 + 17位时间戳 + 9位id补零 + 4位随机数 合计32位
+     *
+     * @param head 例如 商品-SP 退款-TK 等等
+     * @param id   用户id
+     * @return
+     */
+    public String createOrderNo(String head, Long id) {
+        StringBuilder uid = new StringBuilder(id.toString());
+        Date date = new Date();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
+        int length = uid.length();
+        for (int i = 0; i < 9 - length; i++) {
+            uid.insert(0, "0");
+        }
+        return head + sdf.format(date) + uid + (int) ((Math.random() * 9 + 1) * 1000);
+    }
+
+    /**
+     * 金额元转分字符串
+     *
+     * @param cny 元
+     * @return
+     */
+    public Integer amount_fee(BigDecimal cny) {
+        BigDecimal b2 = new BigDecimal("100");
+        return cny.multiply(b2).setScale(0, RoundingMode.DOWN).intValue();
+    }
+
+
+    public AppUserPayForm createOrder(@Valid AppLevelOrderForm appLevelOrderForm) {
+        Long userId = SecurityUtils.getUserId();
+        String userOpenId = userInfoMapper.getAppletUserInfo(userId).getOpenid();
+        String orderNo = createOrderNo("SP", userId);
+        //创建订单
+        UserOrderInfo orderInfo = new UserOrderInfo();
+        orderInfo.setUserId(userId);
+        orderInfo.setOrderNo(orderNo);
+        orderInfo.setLevelId(appLevelOrderForm.getLevelId());
+        orderInfo.setOpenid(userOpenId);
+
+        userOrderInfoMapper.insert(orderInfo);
+
+        //构建支付表单返回给前端支撑JsApi支付调用
+        AppUserPayForm payForm = new AppUserPayForm();
+        payForm.setOrderId(orderInfo.getId()).setOrderNo(orderNo);
+
+        //查询档位
+        RechargeLevel level = rechargeLevelMapper.selectById(appLevelOrderForm.getLevelId());
+
+        PayUtill wx = new PayUtill();
+
+        /**
+         * 小程序支付
+         */
+        String payWay = "1";
+
+        /**
+         * 请求支付
+         */
+        try {
+            log.debug("通知第三开始支付:");
+            // 通知第三方支付----------------------------------------------------------
+
+            SortedMap map = new TreeMap<>();
+            // 订单编号
+            map.put("out_trade_no", orderNo);
+            // 商品描述
+            map.put("body", "购买充电抵扣券");
+            // 附加信息
+            map.put("attach", "支付人" + userId);
+
+            // pifList.get(0).setHydOrderPayMoney(new BigDecimal("0.01"));
+            // 总金额(分)
+
+            map.put("total_fee", 1);
+            // 终端ip
+            map.put("mch_create_ip", appLevelOrderForm.getIp());
+            // 签名方式
+            map.put("sign_type", "RSA_1_256");
+            // 回调地址
+            map.put("notify_url", swiftpassConfig.getNotify_url());
+            // 公众账号或小程序ID
+            map.put("sub_appid", "wx9894a01b9e92c368");
+            if (payWay.equals("1")) {// 支付渠道(1 微信 2支付宝支付 4建行支付 6微信小程序支付)
+                // 是否小程序支付--值为1,表示小程序支付;不传或值不为1,表示公众账号内支付
+                map.put("is_minipg", "1");
+                map.put("sub_appid", "wx9894a01b9e92c368");
+            }else if(payWay.equals("2")){
+                map.put("is_minipg", "2");
+            }
+
+            // --------微信支付请求
+            Map<String, Object> wxMap = wx.pay(map, userOpenId,swiftpassConfig);
+            if (wxMap.get("status").toString().equals("200")) {
+                Map accountMap = new HashMap();
+                accountMap.put("wx", wxMap);
+                payForm.setParams(accountMap);
+                return payForm;
+            } else {
+                throw new RuntimeException("请求支付失败。");
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        throw new RuntimeException("请求支付失败。");
+    }
+}

+ 8 - 4
src/main/java/com/zsElectric/boot/business/service/impl/ChargeOrderInfoServiceImpl.java

@@ -9,6 +9,7 @@ import com.zsElectric.boot.business.model.form.applet.AppStopChargeForm;
 import com.zsElectric.boot.business.model.query.applet.AppChargeOrderInfoQuery;
 import com.zsElectric.boot.business.model.vo.applet.AppChargeVO;
 import com.zsElectric.boot.business.model.vo.applet.AppUserInfoVO;
+import com.zsElectric.boot.business.service.AppletHomeService;
 import com.zsElectric.boot.business.service.UserAccountService;
 import com.zsElectric.boot.charging.dto.StartChargingRequestDTO;
 import com.zsElectric.boot.charging.dto.StartChargingResponseVO;
@@ -66,6 +67,8 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
 
     private final UserAccountService userAccountService;
 
+    private final AppletHomeService appletHomeService;
+
     //充电订单号前缀
     private final String ORDER_NO_PREFIX = "CD";
     //设备流水号前缀
@@ -197,8 +200,9 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
             chargeOrderInfo.setPhoneNum(userInfo.getPhone());
             chargeOrderInfo.setThirdPartyStationId(formData.getStationId());
             chargeOrderInfo.setOrderType(formData.getOrderType());
-            //todo 预支付金额
-            //chargeOrderInfo.setPreAmt();
+            //预支付金额
+            BigDecimal preAmt = appletHomeService.calculateAvailableChargingAmount(formData.getConnectorInfoId());
+            chargeOrderInfo.setPreAmt(preAmt);
 
             //判断用户是否绑定企业
             if (ObjectUtil.isNotEmpty(userInfo.getFirmId())) {
@@ -224,8 +228,8 @@ public class ChargeOrderInfoServiceImpl extends ServiceImpl<ChargeOrderInfoMappe
                     .setStartChargeSeq(startChargeSeq)
                     .setConnectorID(formData.getConnectorId())
                     .setPhoneNum(userInfo.getPhone())
-            //预支付金额
-            //.setChargingAmt()
+                    //预支付金额
+                    .setChargingAmt(preAmt.toString())
             ;
             StartChargingResponseVO startChargingResponseVO = chargingBusinessService.startCharging(requestDTO);
             if (!Objects.equals(startChargingResponseVO.getSuccStat(), SystemConstants.STATUS_ZERO)) {

+ 7 - 1
src/main/java/com/zsElectric/boot/business/service/impl/UserOrderInfoServiceImpl.java

@@ -12,6 +12,7 @@ import com.zsElectric.boot.business.model.entity.*;
 import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
 import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
 import com.zsElectric.boot.business.model.query.applet.AppUserOrderInfoQuery;
+import com.zsElectric.boot.business.model.vo.applet.AppUserInfoVO;
 import com.zsElectric.boot.business.service.*;
 import com.zsElectric.boot.common.constant.SystemConstants;
 import com.zsElectric.boot.core.exception.BusinessException;
@@ -156,6 +157,11 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
         return pageVO;
     }
 
+    @Override
+    public AppUserInfoVO getAppletUserInfo(Long userId){
+        return userInfoMapper.getAppletUserInfo(userId);
+    }
+
     /**
      * 创建订单
      *
@@ -165,7 +171,7 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
     @Override
     public AppUserPayForm createOrder(AppLevelOrderForm appLevelOrderForm) {
         Long userId = SecurityUtils.getUserId();
-        String userOpenId = userInfoMapper.getAppletUserInfo(userId).getOpenid();
+        String userOpenId = this.getAppletUserInfo(userId).getOpenid();
         String orderNo = createOrderNo("SP", userId);
         //创建订单
         UserOrderInfo orderInfo = new UserOrderInfo();

+ 90 - 0
src/main/java/com/zsElectric/boot/core/pay/WFT/WFTConstants.java

@@ -0,0 +1,90 @@
+package com.zsElectric.boot.core.pay.WFT;
+
+/**
+ * 威富通支付相关配置常量
+ *
+ * @author zsElectric
+ * @since 2025-12-29
+ */
+public interface WFTConstants {
+
+    /**
+     * 威富通支付网关地址
+     */
+    String WFT_GATEWAY_URL = "https://pay.swiftpass.cn/pay/gateway";
+
+    /**
+     * 接口类型 - JSAPI支付
+     */
+    String SERVICE_TYPE_JSPAY = "pay.weixin.jspay";
+
+    /**
+     * 版本号
+     */
+    String VERSION = "2.0";
+
+    /**
+     * 字符集
+     */
+    String CHARSET = "UTF-8";
+
+    /**
+     * md5密钥
+     */
+    String md5key = "f5131b3f07acb965a59041b690a29911";
+
+    /**
+     * 签名类型 - MD5
+     */
+    String SIGN_TYPE_MD5 = "MD5";
+
+    /**
+     * 签名类型 - RSA_1_256
+     */
+    String SIGN_TYPE_RSA_256 = "RSA_1_256";
+
+    /**
+     * 签名类型 - RSA_1_1
+     */
+    String SIGN_TYPE_RSA_1 = "RSA_1_1";
+
+    /**
+     * 商户号(从配置文件读取)
+     */
+    String WFT_MCH_ID = "105520131602";
+
+    /**
+     * 商户私钥(从配置文件读取)
+     */
+    String WFT_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDeFB5G2OYT762PpUytCw7Du40i6WnzcmbvEE9IXPXi+QirPwMvW9mBqNDIUk5hQS3ZnHjj80YQRWG6yksjE6kHAYIWahCDiaPlBqYvYSJ8ePzbT61THZJbzqFaIG3svW7xq9nsUmzVBub0ATIzC1DQRu9ZTdrj/iuMUEhJyJ8IHrTP09eTwNYdoagHQlKWRVoNE3LuU4GXG3VCbkQ2ixbMo8dXBisDIi3GYOSFWzota6H+OCp9Mta1jTqdwALKAU9PNlRkQwOMLk2OmMqGUhImVVpl+eGrIYn3iARce0alNFg8hghFMJ8MKpSxJDM6YHNOJQ06S25YYhTpd+C2/VOBAgMBAAECggEBAM/W9ksJ/bJU0xOn+W3N9oB7C+jLmMwtmmZM1lZ8IefNeC6Ep59wD81ISDXiydY9YQLTbVSxPjZGKOPfJZjrcnrLD4uYsmHYtFnI8klPWC40MTmzhRxPhcWESgAGb7prw+RMGIUS0yY/8nAUmn2pLnXunVzv/1b3bpxAGpdrOmMmU28GBt9AlXiIpVmnxnkhp66c4zFj5gvvVoDrz9m/6Acyn6n7yccCHD2iYw0D78GPItEWky38tH/FV0lRcCCYAf5fc2nFnicrdgj1RYjqTWxM7A92UecviTAbiuPO6CQQl7+sMtGU2d6UeKj0Xrrl2gly+lOS97P/NcLtZf5vklECgYEA8SMlCg5AToSkIwal7dXYgM2VlNFwSRDuO3hEVoWe7bM/LkEp8dqSpV025ZtngTY/qziXsGP/7l8bcS1th19cX/+/MFFOWsoxtqvOam2Elp+Qg1johEfnWI+Bo4WiQ5CHYNQRN3cuiWTc5HHuI0KQAEPx/aYogD057X2FIsiu6S0CgYEA68RAFsupZ6R8BhJbfY2CK07XLNvXu5DAYBmU3trmQFnaxXeQFfhQ8hi9m5Awu14YCnRmHzc8+QXFD/GqTAbxtImc6AZQKWdLuUntkitPWmJK6dtJC8Is3U9Yqz5+CkSmgZSfqW0DvEt7jagoNfpKgy29Qq4r35b6JsDXtnTQICUCgYEAjmwnkEzihn2pRFbE4jiP62OBmagqHb22N8HM+x1oxRQ9mOA8GfDy9GCd7/ddpt+Xs1V1omUt4GikGLCwJGiacsjm727WTKFnw3CuNgYBbcVI4Ys9qgOeDJyWATMIp8dRbktS7+OgxN2h6fuwn3rM+psm7p2ZBkUjVbXxUJ4fUPECgYEAw1wmAv2VjRUF0/4oI5w7bWlx8XDljT1/uuHXsuZN/qq2FgRht2LAqCsKCjprtwZcA2W6LUmXU32Ncg29ICxs4j1ZcAWzLOu0GoAAxKrwoSNrkeYr2/t1M5kJDzTEOfvywNMHjduQSdl+Mr5RO5D/Zz1iYztxjV9MPwpydHTM9KUCgYAHuT98NkBilMxQNNmUB13E10MYQvZuiGVFZtT3up69Elpmtm7Z5cEW7QNG0g1LPPfkzfWPsq+6I98FmozLickqvjntdpul4czTITn8SNHqoxvdbcVnDipF1KwlHcnXhjO1KjSZg3a/iv554OR3/rbD9SWDzDAT7Zy6zX9n8OwGRA==";
+
+    /**
+     * 威富通平台公钥(用于验签)
+     */
+    String WFT_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2HOacYJOO9zsWPVmauV/YCeR78RsRDtusdLEngi/JkPkZVSE0X47z2RpJncGyV1QfdHv0udVEND4bvjXku4qUJp5DYAulm6pDXdcwWPcdI77V7dqoDvYm9Cc8kGj9s+/0xeuxX4qJmwzFTf7XjRfTT7+OVSvFnnButAkgMuD3cW1rtcQYeY9S9puQneN1i1+Lek5GCpW5PFsezK6QMgrpB1TFVSF5tloUODfc4fBDY5quGxn29Fo9gzJXO8ehoRft/JEaS4rNqmlfbvaJfEROXALlKoUX8Iki050ss7WwIBS6xuV08JnHTUHzHmAzOscwyYmT3RZChPgluWuyYW30wIDAQAB";
+
+    /**
+     * 支付通知回调地址
+     */
+    String WFT_NOTIFY_URL = "https://1477cfb9.r28.cpolar.top/api/wft/payNotify";
+
+    /**
+     * 原生JS标识
+     */
+    String IS_RAW = "1";
+
+    /**
+     * 小程序支付标识
+     */
+    String IS_MINIPG = "1";
+
+    /**
+     * 返回状态码 - 成功
+     */
+    String STATUS_SUCCESS = "0";
+
+    /**
+     * 业务结果 - 成功
+     */
+    String RESULT_CODE_SUCCESS = "0";
+}

+ 122 - 0
src/main/java/com/zsElectric/boot/core/pay/WFT/WFTPayUtils.java

@@ -0,0 +1,122 @@
+package com.zsElectric.boot.core.pay.WFT;
+
+import cn.hutool.core.util.StrUtil;
+import com.zsElectric.boot.core.pay.WFT.WFTConstants;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 威富通支付工具类
+ *
+ * @author zsElectric
+ * @since 2025-12-29
+ */
+@Slf4j
+public class WFTPayUtils {
+
+    private static final MediaType XML_MEDIA_TYPE = MediaType.parse("application/xml; charset=utf-8");
+    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
+            .connectTimeout(30, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .writeTimeout(30, TimeUnit.SECONDS)
+            .build();
+
+    /**
+     * 构建XML请求体
+     *
+     * @param params 参数Map
+     * @return XML字符串
+     */
+    public static String buildXmlRequest(Map<String, String> params) {
+        StringBuilder xml = new StringBuilder("<xml>");
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            if (StrUtil.isNotEmpty(entry.getValue())) {
+                xml.append("<").append(entry.getKey()).append(">")
+                        .append("<![CDATA[").append(entry.getValue()).append("]]>")
+                        .append("</").append(entry.getKey()).append(">");
+            }
+        }
+        xml.append("</xml>");
+        return xml.toString();
+    }
+
+    /**
+     * 解析XML响应
+     *
+     * @param xml XML字符串
+     * @return 参数Map
+     * @throws Exception 解析异常
+     */
+    public static Map<String, String> parseXmlResponse(String xml) throws Exception {
+        Map<String, String> result = new HashMap<>();
+
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        // 防止XXE攻击
+        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        InputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+        Document doc = builder.parse(is);
+
+        NodeList nodeList = doc.getDocumentElement().getChildNodes();
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                Element element = (Element) node;
+                result.put(element.getNodeName(), element.getTextContent());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 发送POST请求
+     *
+     * @param url     请求URL
+     * @param xmlData XML数据
+     * @return 响应XML字符串
+     * @throws Exception 请求异常
+     */
+    public static String sendPostRequest(String url, String xmlData) throws Exception {
+        RequestBody body = RequestBody.create(xmlData, XML_MEDIA_TYPE);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+
+        try (Response response = HTTP_CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new RuntimeException("HTTP请求失败,状态码:" + response.code());
+            }
+            ResponseBody responseBody = response.body();
+            if (responseBody == null) {
+                throw new RuntimeException("响应体为空");
+            }
+            return responseBody.string();
+        }
+    }
+
+}

+ 2 - 2
src/main/java/com/zsElectric/boot/core/pay/WechatConstants.java

@@ -20,9 +20,9 @@ public interface WechatConstants {
     String WECHAT_MCH_PRIVATE_KEY = "";
 
     //微信小程序appid
-    String WECHAT_APPID = "wx52bee24aece42dd9";
+    String WECHAT_APPID = "wx9894a01b9e92c368";
 
     //微信小程序密钥
-    String WECHAT_SECRET = "9c1dd607955f42ab4fabfd898839eb88";
+    String WECHAT_SECRET = "b1e83dbcf83af310c38c0a138739ddcf";
 
 }

+ 1 - 1
src/main/java/com/zsElectric/boot/core/pay/WechatUrlConstants.java

@@ -32,7 +32,7 @@ public interface WechatUrlConstants {
     String PAY_V2_REFUND_NOTIFY = "https://xxx.com/api/wechatPay/wechatRefundNotify";
 
     //微信支付v3 支付通知接口地址
-    String PAY_V3_NOTIFY = "https://cd.admin.zswlgz.com/applet/v1/order/wechatPayNotify";
+    String PAY_V3_NOTIFY = "https://1477cfb9.r28.cpolar.top/applet/v1/wft/order/notify";
 
     //微信支付v3 退款通知接口地址
     String PAY_V3_REFUND_NOTIFY = "https://cd.admin.zswlgz.com/applet/v1/order/callback/refundOrderNotify";

+ 123 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/config/SwiftpassConfig.java

@@ -0,0 +1,123 @@
+package com.zsElectric.boot.core.pay.swiftpass.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+
+/**
+ * <功能详细描述>配置信息
+ * 
+ * @author  Administrator
+ * @version  [版本号, 2018-2-1]
+ * @since  [产品/模块版本]
+ */
+@Configuration
+public class SwiftpassConfig {
+
+    /**
+     * 微信小程序相关
+     */
+
+    /**
+     * 交易密钥
+     */
+    @Value("${wx.key}")
+    private  String key ;
+    @Value("${wx.mchPrivateKey}")
+    private  String mchPrivateKey;
+    @Value("${wx.platPublicKey}")
+    private  String platPublicKey;
+    /**
+     * 商户号
+     */
+    @Value("${wx.mch_id}")
+    private  String mch_id;
+    /**
+     * appid
+     */
+    @Value("${wx.appid}")
+    private  String appid;
+    /**
+     * secret
+     */
+    @Value("${wx.secret}")
+    private  String secret;
+    /**
+     * 请求url
+     */
+    @Value("${wx.req_url}")
+    private  String req_url;
+
+    /**
+     * 通知url
+     */
+    @Value("${wx.notify_url}")
+    private  String notify_url;
+
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getMchPrivateKey() {
+        return mchPrivateKey;
+    }
+
+    public void setMchPrivateKey(String mchPrivateKey) {
+        this.mchPrivateKey = mchPrivateKey;
+    }
+
+    public String getPlatPublicKey() {
+        return platPublicKey;
+    }
+
+    public void setPlatPublicKey(String platPublicKey) {
+        this.platPublicKey = platPublicKey;
+    }
+
+    public String getMch_id() {
+        return mch_id;
+    }
+
+    public void setMch_id(String mch_id) {
+        this.mch_id = mch_id;
+    }
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public String getReq_url() {
+        return req_url;
+    }
+
+    public void setReq_url(String req_url) {
+        this.req_url = req_url;
+    }
+
+    public String getNotify_url() {
+        return notify_url;
+    }
+
+    public void setNotify_url(String notify_url) {
+        this.notify_url = notify_url;
+    }
+
+}

+ 70 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/MD5.java

@@ -0,0 +1,70 @@
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.SignatureException;
+
+/** 
+* 功能:MD5签名
+* 版本:3.3
+* 修改日期:2012-08-17
+* */
+public class MD5 {
+    public static void main(String[] args) {
+        String str = "appId=wx8632a91376b81e24&callback_url=http://u.shdplan.com/usr/details.html?paySuc=true&awaId=AID201508100946430003&money=1.99&nonceStr=1441076469693&package=prepay_id=wx2015090111010993f0dd52c80787614418&signType=MD5&status=0&timeStamp=1441076469693";
+        System.out.println(MD5.sign(str, "&key=8748fd966d0ffc47abd59e97", "utf-8"));
+        
+    }
+
+    /**
+     * 签名字符串
+     * @param text 需要签名的字符串
+     * @param key 密钥
+     * @param input_charset 编码格式
+     * @return 签名结果
+     */
+    public static String sign(String text, String key, String input_charset) {
+    	text = text + key;
+    	System.out.println(text);
+        return DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
+    }
+    
+    /**
+     * 签名字符串
+     * @param text 需要签名的字符串
+     * @param sign 签名结果
+     * @param key 密钥
+     * @param input_charset 编码格式
+     * @return 签名结果
+     */
+    public static boolean verify(String text, String sign, String key, String input_charset) {
+    	text = text + key;
+    	String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
+    	if(mysign.equals(sign)) {
+    		return true;
+    	}
+    	else {
+    		return false;
+    	}
+    }
+
+    /**
+     * @param content
+     * @param charset
+     * @return
+     * @throws SignatureException
+     * @throws UnsupportedEncodingException 
+     */
+    private static byte[] getContentBytes(String content, String charset) {
+        if (charset == null || "".equals(charset)) {
+            return content.getBytes();
+        }
+        try {
+            return content.getBytes(charset);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
+        }
+    }
+
+}

+ 339 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/PayUtill.java

@@ -0,0 +1,339 @@
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import com.zsElectric.boot.core.pay.swiftpass.config.SwiftpassConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+
+@Slf4j
+public class PayUtill {
+
+    private final static String version = "2.0";
+    private final static String charset = "UTF-8";
+    //private final static String sign_type = "MD5";
+   /* @Autowired
+	private SwiftpassConfig sws;*/
+
+    /**
+     * 微信支付请求
+     * @throws ServletException
+     * @throws IOException
+     * @see [类、类#方法、类#成员]
+     */
+    public Map<String,Object> pay(SortedMap<String, String> map, String openid, SwiftpassConfig sws) throws ServletException, IOException {
+        log.info("发起微信支付请求...");
+
+        map.put("service", "pay.weixin.jspay");
+        map.put("version", version);
+        map.put("charset", charset);
+
+        map.put("is_raw", "1");
+        map.put("mch_id", sws.getMch_id());//商户号
+        map.put("nonce_str", String.valueOf(new Date().getTime()));//随机字符窜
+        map.put("sub_openid", openid);
+
+        Map<String, String> params = SignUtils.paraFilter(map);
+        StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+        SignUtils.buildPayParams(buf, params, false);
+        String preStr = buf.toString();
+        String sign_type = map.get("sign_type");//签名方式
+        map.put("sign", SignUtil.getSign(sign_type, preStr,sws));
+        String reqUrl = sws.getReq_url();
+        log.info("请求xml:"+XmlUtils.toXml(map));
+        CloseableHttpResponse response = null;
+        CloseableHttpClient client = null;
+        String res = null;
+        Map<String, String> resultMap = null;
+        try {
+            HttpPost httpPost = new HttpPost(reqUrl);
+            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
+            httpPost.setEntity(entityParams);
+            httpPost.setHeader("Content-Type", "text/xml;utf-8");
+            client = HttpClients.createDefault();
+            response = client.execute(httpPost);
+            if (response != null && response.getEntity() != null) {
+                resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+                String reSign = resultMap.get("sign");
+                sign_type = resultMap.get("sign_type");
+                res = XmlUtils.toXml(resultMap);
+                log.info("签名方式"+sign_type);
+                log.info("请求结果:" + res);
+                boolean boolSin = SignUtil.verifySign(reSign, sign_type, resultMap,sws);
+                log.info("验证结果:"+boolSin);
+                if (resultMap.containsKey("sign") && !boolSin) {
+                    res = "验证签名不通过";
+                } else {
+                    if ("0".equals(resultMap.get("status")) && "0".equals(resultMap.get("result_code"))) {
+                        String pay_info = resultMap.get("pay_info");
+                        System.out.println("pay_info : " + pay_info);
+                        //                       log.debug("pay_info : " + pay_info);
+                        res = "ok";
+                    }
+                }
+            } else {
+                res = "操作失败";
+            }
+        } catch (Exception e) {
+            log.error("操作失败,原因:",e);
+            res = "系统异常";
+        } finally {
+            if (response != null) {
+                response.close();
+            }
+            if (client != null) {
+                client.close();
+            }
+        }
+        Map<String,String> result = new HashMap<String,String>();
+        if("ok".equals(res)){
+            result = resultMap;
+            result.put("status", "200");
+        }else{
+            result.put("status", "500");
+            result.put("msg", res);
+        }
+        return result;
+    }
+
+
+
+    /**
+     * 订单查询
+     * @param req
+     * @param resp
+     * @throws ServletException
+     * @throws IOException
+     * @see [类、类#方法、类#成员]
+     */
+    public Map query(HttpServletRequest req, HttpServletResponse resp, SwiftpassConfig sws) throws ServletException, IOException{
+        log.info("订单查询...");
+        SortedMap<String,String> map = XmlUtils.getParameterMap(req);
+
+        map.put("service", "unified.trade.query");
+        map.put("version", version);
+        map.put("charset", charset);
+        map.put("mch_id", sws.getMch_id());
+
+        String reqUrl = sws.getReq_url();
+        map.put("nonce_str", String.valueOf(new Date().getTime()));
+
+        Map<String,String> params = SignUtils.paraFilter(map);
+        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
+        SignUtils.buildPayParams(buf,params,false);
+        String preStr = buf.toString();
+        String sign_type = map.get("sign_type");
+
+        map.put("sign", SignUtil.getSign(sign_type, preStr,sws));
+
+
+        CloseableHttpResponse response = null;
+        CloseableHttpClient client = null;
+        String res = null;
+        try {
+            HttpPost httpPost = new HttpPost(reqUrl);
+            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
+            httpPost.setEntity(entityParams);
+            httpPost.setHeader("Content-Type", "text/xml;utf-8");
+            client = HttpClients.createDefault();
+            response = client.execute(httpPost);
+
+            if(response != null && response.getEntity() != null){
+                Map<String,String> resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+                String reSign = resultMap.get("sign");
+                sign_type = resultMap.get("sign_type");
+                res = XmlUtils.toXml(resultMap);
+                log.info("签名方式"+sign_type);
+                log.info("请求结果:" + res);
+                if (resultMap.containsKey("sign") && !SignUtil.verifySign(reSign, sign_type, resultMap,sws)) {
+                    res = "验证签名不通过";
+                }else{
+                    if("0".equals(resultMap.get("status"))){
+                        if("0".equals(resultMap.get("result_code"))){
+                            String trade_state = resultMap.get("trade_state");
+                        }else{
+                        }
+                    }else{
+                    }
+                }
+            }else{
+                res = "操作失败!";
+            }
+        } catch (Exception e) {
+            log.error("操作失败,原因:",e);
+            res = "操作失败";
+        } finally {
+            if(response != null){
+                response.close();
+            }
+            if(client != null){
+                client.close();
+            }
+        }
+        Map<String,String> result = new HashMap<String,String>();
+        if(res.startsWith("<")){
+            result.put("status", "200");
+            result.put("msg", "操作成功,请在日志文件中查看");
+        }else{
+            result.put("status", "500");
+            result.put("msg", res);
+        }
+        return result;
+    }
+
+    /**
+     * 退款查询
+     * @throws ServletException
+     * @throws IOException
+     * @see [类、类#方法、类#成员]
+     */
+    public Map refundQuery(SortedMap<String, String> map,SwiftpassConfig sws) throws Exception{
+        log.info("退款查询...");
+
+        map.put("service", "unified.trade.refundquery");
+        map.put("version", version);
+        map.put("charset", charset);
+
+        String reqUrl = sws.getReq_url();
+        map.put("mch_id", sws.getMch_id());
+        map.put("nonce_str", String.valueOf(new Date().getTime()));
+
+        Map<String,String> params = SignUtils.paraFilter(map);
+        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
+        SignUtils.buildPayParams(buf,params,false);
+        String preStr = buf.toString();
+        String sign_type = map.get("sign_type").toString();
+
+        map.put("sign", SignUtil.getSign(sign_type, preStr,sws));
+        Map<String,String> resultMap = null;
+
+        CloseableHttpResponse response = null;
+        CloseableHttpClient client = null;
+        String res = null;
+        try {
+            HttpPost httpPost = new HttpPost(reqUrl);
+            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
+            httpPost.setEntity(entityParams);
+            httpPost.setHeader("Content-Type", "text/xml;utf-8");
+            client = HttpClients.createDefault();
+            response = client.execute(httpPost);
+            if(response != null && response.getEntity() != null){
+                resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+                String reSign = resultMap.get("sign");
+                sign_type = resultMap.get("sign_type");
+                res = XmlUtils.toXml(resultMap);
+                log.info("签名方式"+sign_type);
+                log.info("请求结果:" + res);
+                if (resultMap.containsKey("sign") && !SignUtil.verifySign(reSign, sign_type, resultMap,sws)) {
+                    res = "验证签名不通过";
+                }
+            }else{
+                res = "操作失败!";
+            }
+        } catch (Exception e) {
+            log.error("操作失败,原因:",e);
+            res = "操作失败";
+        } finally {
+            if(response != null){
+                response.close();
+            }
+            if(client != null){
+                client.close();
+            }
+        }
+        Map<String,Object> result = new HashMap<String,Object>();
+        if(res.startsWith("<")){
+            result.put("status", "200");
+            result.put("data", resultMap);
+        }else{
+            result.put("status", "500");
+            result.put("msg", res);
+        }
+        return result;
+    }
+    /**
+     * 退款
+     * @throws ServletException
+     * @throws IOException
+     * @see [类、类#方法、类#成员]
+     */
+    public Map refund(SortedMap<String, String> map,SwiftpassConfig sws) throws ServletException, IOException{
+        log.debug("发起微信退款...");
+
+        map.put("service", "unified.trade.refund");
+        map.put("version", version);
+        map.put("charset", charset);
+        String reqUrl = sws.getReq_url();
+        map.put("mch_id", sws.getMch_id());
+        map.put("op_user_id", sws.getMch_id());
+        map.put("nonce_str", String.valueOf(new Date().getTime()));
+
+        Map<String,String> params = SignUtils.paraFilter(map);
+        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
+        SignUtils.buildPayParams(buf,params,false);
+        String preStr = buf.toString();
+        String sign_type = map.get("sign_type");
+
+        map.put("sign", SignUtil.getSign(sign_type, preStr,sws));
+
+        CloseableHttpResponse response = null;
+        CloseableHttpClient client = null;
+        String res = null;
+        Map<String,String> resultMap = null;
+        System.out.println("请求xml:"+XmlUtils.toXml(map));
+        try {
+            HttpPost httpPost = new HttpPost(reqUrl);
+            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
+            httpPost.setEntity(entityParams);
+            httpPost.setHeader("Content-Type", "text/xml;utf-8");
+            client = HttpClients.createDefault();
+            response = client.execute(httpPost);
+            if(response != null && response.getEntity() != null){
+                resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+                String reSign = resultMap.get("sign");
+                sign_type = resultMap.get("sign_type");
+                res = XmlUtils.toXml(resultMap);
+                log.info("签名方式"+sign_type);
+                log.info("请求结果:" + res);
+                if (resultMap.containsKey("sign") && !SignUtil.verifySign(reSign, sign_type, resultMap,sws)) {
+                    res = "验证签名不通过";
+                }
+            }else{
+                res = "操作失败!";
+            }
+        } catch (Exception e) {
+            log.error("操作失败,原因:",e);
+            res = "操作失败";
+        } finally {
+            if(response != null){
+                response.close();
+            }
+            if(client != null){
+                client.close();
+            }
+        }
+        Map<String,String> result = new HashMap<String,String>();
+        if(res.startsWith("<")){
+            return resultMap;
+        }else{
+            result.put("status", "500");
+            result.put("msg", res);
+        }
+        return resultMap;
+    }
+
+
+
+
+}

+ 93 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/RSAUtil.java

@@ -0,0 +1,93 @@
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+
+/**
+ * @author zeming.fan@swiftpass.cn
+ *
+ */
+public class RSAUtil {
+    public static enum SignatureSuite {
+        //SHA1("SHA1WithRSA"), MD5("MD5WithRSA");
+    	SHA1("SHA1WithRSA"), SHA256("SHA256WithRSA");
+        private String suite;
+
+        SignatureSuite(String suite) {
+            this.suite = suite;
+        }
+
+        public String val() {
+            return suite;
+        }
+    }
+
+    //private final static Logger logger = LoggerFactory.getLogger(RSAUtil.class);
+
+    private static KeyFactory getKeyFactory() {
+        try {
+            return KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            // 应该不会出现
+            throw new RuntimeException("初始化RSA KeyFactory失败");
+        }
+    }
+
+    public static byte[] sign(SignatureSuite suite, byte[] msgBuf, String privateKeyStr) {
+        Signature signature = null;
+        try {
+            signature = Signature.getInstance(suite.val());
+        } catch (Exception e) {
+            // 上线运行时套件一定存在
+            // 异常不往外抛
+        }
+
+        try {
+            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
+            PrivateKey privateKey = getKeyFactory().generatePrivate(keySpec);
+            signature.initSign(privateKey);
+        } catch(Exception e) {
+            //logger.warn("解析私钥失败:{}", e.getMessage());
+            throw new RuntimeException("INVALID_PRIKEY");
+        }
+        try {
+            signature.update(msgBuf);
+            return signature.sign();
+        } catch (SignatureException e) {
+            // 一般不会出现
+            
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    public static boolean verifySign(SignatureSuite suite, byte[] msgBuf, byte[] sign, String publicKeyStr) {
+        Signature signature = null;
+        try {
+            signature = Signature.getInstance(suite.val());
+        } catch (Exception e) {
+            // 上线运行时套件一定存在
+            // 异常不往外抛
+        }
+
+        try {
+            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
+            PublicKey publicKey = getKeyFactory().generatePublic(keySpec);
+            signature.initVerify(publicKey);
+        } catch(Exception e) {
+           
+            throw new RuntimeException("INVALID_PUBKEY");
+        }
+        try {
+            signature.update(msgBuf);
+            return signature.verify(sign);
+        } catch (SignatureException e) {
+            // 一般不会出现
+            
+            throw new RuntimeException("签名格式不合法");
+        }
+    }
+}

+ 140 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/SignUtil.java

@@ -0,0 +1,140 @@
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import com.zsElectric.boot.core.pay.swiftpass.config.SwiftpassConfig;
+import org.apache.commons.codec.binary.Base64;
+
+import java.util.*;
+
+
+/**
+ * @author zeming.fan@swiftpass.cn
+ *
+ */
+public class SignUtil {
+	
+	//请求时根据不同签名方式去生成不同的sign
+    public static String getSign(String signType, String preStr, SwiftpassConfig sw){
+    	if("RSA_1_256".equals(signType)){
+        	try {
+        		return SignUtil.sign(preStr,"RSA_1_256",sw.getMchPrivateKey());
+			} catch (Exception e1) {
+				// TODO Auto-generated catch block
+				e1.printStackTrace();
+			}
+        }else{
+        	return MD5.sign(preStr, "&key=" + sw.getKey(), "utf-8");
+        }
+    	return null;
+    }
+    
+    //对返回参数的验证签名
+    public static boolean verifySign(String sign,String signType,Map<String,String> resultMap,SwiftpassConfig sw) throws Exception{
+    	System.out.println("签名方式:"+signType);
+    	System.out.println("签名:"+sign);
+    	System.out.println("返回的结果:"+resultMap);
+    	System.out.println("签名公钥:"+sw.getPlatPublicKey());
+    	if("RSA_1_256".equals(signType)){
+    		Map<String,String> Reparams = SignUtils.paraFilter(resultMap);
+            StringBuilder Rebuf = new StringBuilder((Reparams.size() +1) * 10);
+            SignUtils.buildPayParams(Rebuf,Reparams,false);
+            String RepreStr = Rebuf.toString();
+            if(SignUtil.verifySign(RepreStr,sign, signType,sw.getPlatPublicKey())){
+            	System.out.println("已经验证了签名,且验证通过");
+            	return true;
+            }
+    	}else if("MD5".equals(signType)){
+    		if(SignUtils.checkParam(resultMap, sw.getKey())){
+    			return true;
+    		}
+    	}
+    	return false;
+    }
+	public static boolean verifySign(String preStr,String sign,String signType, String platPublicKey) throws Exception {
+		// 调用这个函数前需要先判断是MD5还是RSA
+		// 商户的验签函数要同时支持MD5和RSA
+		RSAUtil.SignatureSuite suite = null;
+		
+		if ("RSA_1_1".equals(signType)) {
+			suite = RSAUtil.SignatureSuite.SHA1;
+		} else if ("RSA_1_256".equals(signType)) {
+			suite = RSAUtil.SignatureSuite.SHA256;
+		} else {
+			throw new Exception("不支持的签名方式");
+		}
+        
+		boolean result = RSAUtil.verifySign(suite, preStr.getBytes("UTF8"), Base64.decodeBase64(sign.getBytes("UTF8")),
+                platPublicKey);
+        
+		return result;
+    }
+
+    public static String sign(String preStr, String signType, String mchPrivateKey) throws Exception {
+		RSAUtil.SignatureSuite suite = null;
+		if ("RSA_1_1".equals(signType)) {
+			suite = RSAUtil.SignatureSuite.SHA1;
+		} else if ("RSA_1_256".equals(signType)) {
+			suite = RSAUtil.SignatureSuite.SHA256;
+		} else {
+			throw new Exception("不支持的签名方式");
+		}
+        byte[] signBuf = RSAUtil.sign(suite, preStr.getBytes("UTF8"),
+                mchPrivateKey);
+        return new String(Base64.encodeBase64(signBuf), "UTF8");
+    }
+    
+    public static void main(String[] args) throws Exception {
+
+    	String xml = "<xml><attach><![CDATA[支付人匿名]]></attach>\n" +
+				"<body><![CDATA[购票]]></body>\n" +
+				"<charset><![CDATA[UTF-8]]></charset>\n" +
+				"<is_minipg><![CDATA[1]]></is_minipg>\n" +
+				"<is_raw><![CDATA[1]]></is_raw>\n" +
+				"<mch_create_ip><![CDATA[172.16.0.25]]></mch_create_ip>\n" +
+				"<mch_id><![CDATA[105550131844]]></mch_id>\n" +
+				"<nonce_str><![CDATA[1706770173138]]></nonce_str>\n" +
+				"<notify_url><![CDATA[http://120.78.228.211:8880/orderApi/enditPay]]></notify_url>\n" +
+				"<out_trade_no><![CDATA[zp_ticket_pay_45]]></out_trade_no>\n" +
+				"<service><![CDATA[pay.weixin.jspay]]></service>\n" +
+				"<sign><![CDATA[OG5GZktLKUjGx4+QkqM35r19oiiEtnUeK0ug7dmAjas+USMfdQLUOpbQzVIVPb1drwj8kfeqAXvYpLCbc0oRuGGeSe0fPD3YBN7YA+lrj7fHTcuiaov4jFkz2F/GQF3r6mCVCoNeev/c1P0TGgp1w8CxzDAn+asCzQRaO74WqD6gggCN7m5OqSAots2r1Cm13OGi41Z2MCaahtxLuAT/T+qNP7idfYUYw/MooV3dzmUBEjyZHmgPpKAD1VpPVLwaQTeQblfSWoOkNLXnyTEPINDtVfeFdgBZlYssIy6cPT2Sxh7ZRPjgaXjYDQPUsnfX/r8IvhuAg7MtTk1/Z5kcMQ==]]></sign>\n" +
+				"<sign_type><![CDATA[RSA_1_256]]></sign_type>\n" +
+				"<sub_appid><![CDATA[wx80f7a21fbbe7980c]]></sub_appid>\n" +
+				"<sub_openid><![CDATA[oUCV765SRSydZ2PMlmMzOKvvjv6M]]></sub_openid>\n" +
+				"<total_fee><![CDATA[1]]></total_fee>\n" +
+				"<version><![CDATA[2.0]]></version>\n" +
+				"</xml>";
+
+		//解析xml成map
+		Map<String, String> m = new HashMap<String, String>();
+		//log.info("获取到xml"+sb);
+		System.out.println("开始解析xml");
+		m = XmlUtils.toMap(xml.toString().getBytes(), "utf-8");
+		System.out.println("已经解析过了xml");
+
+		//过滤空 设置 TreeMap
+		SortedMap<Object,Object> packageParams = new TreeMap<Object, Object>();
+		Iterator it = m.keySet().iterator();
+		while (it.hasNext()) {
+			String parameter = (String) it.next();
+			String parameterValue = m.get(parameter);
+
+			String v = "";
+			if(null != parameterValue) {
+				v = parameterValue.trim();
+			}
+			packageParams.put(parameter, v);
+		}
+
+		Map<String,String> Reparams = SignUtils.paraFilter((TreeMap)packageParams);
+		StringBuilder Rebuf = new StringBuilder((Reparams.size() +1) * 10);
+		SignUtils.buildPayParams(Rebuf,Reparams,false);
+		String RepreStr = Rebuf.toString();
+		String sign = "OG5GZktLKUjGx4+QkqM35r19oiiEtnUeK0ug7dmAjas+USMfdQLUOpbQzVIVPb1drwj8kfeqAXvYpLCbc0oRuGGeSe0fPD3YBN7YA+lrj7fHTcuiaov4jFkz2F/GQF3r6mCVCoNeev/c1P0TGgp1w8CxzDAn+asCzQRaO74WqD6gggCN7m5OqSAots2r1Cm13OGi41Z2MCaahtxLuAT/T+qNP7idfYUYw/MooV3dzmUBEjyZHmgPpKAD1VpPVLwaQTeQblfSWoOkNLXnyTEPINDtVfeFdgBZlYssIy6cPT2Sxh7ZRPjgaXjYDQPUsnfX/r8IvhuAg7MtTk1/Z5kcMQ==";
+		String platPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtO+txxud/Tn8CZtcDCutWk1CNFSKzH7Mtpaplq8rm6pGPuUjUC4TMApnDPhLKPD8klF/Y1KPN1wMcvyV3CmUk0etPzqaeXmOP4rsPe9YCBmFtJTEfpTW+5/mM0r5rm9nDjXpcQJWghc3XnWDRUlJwcWgv5oVYsHgCyFv1Lz82A1B9Yjf6f2bd9NCHA3Fc3+xfTHQnOU1LcaLppHhJ8X/JyLCAJkhoZ2W8Ils81pSDqIGnvVxxjOmNCPzeOCfe73F/CJbOpcb2j47ws+p/KmM/xaSaFrhyxFT0PdMwpOTnCrgC6dJFgqdPAPFOJLMqzUG9YX2+o02XNs1Dq+UCLwTnwIDAQAB";
+		System.out.println(verifySign(RepreStr, sign, "RSA_1_256", platPublicKey));
+
+		//    	String preStr = "attach=支付人匿名&body=购票&charset=UTF-8&is_minipg=1&is_raw=1&mch_create_ip=172.16.0.25&mch_id=105550131844&nonce_str=1706757253245&notify_url=http://120.78.228.211:8880/orderApi/enditPay&out_trade_no=zp_ticket_pay_43&service=pay.weixin.jspay&sign=B+CxGl13bnD1yJylWJI/uJNN9Xd0TZ1GR0eQdXL9BByzIWCU4wJofmJfVRTTygo9Asisi1ey0BTwwtLsNJr1uXBKLvodEKp8rAxgm8hRAmy82F6KjjDQtQ4VORDWGHtoAECs2RNcSnsXUtggVj5lO4o0DceYuyPbnGPDI/oCHNbDt9LNQ0FMRKP0LbOFVwaa064qYTyI70xXsl2U/Rh2z5zjIf5CT06evL/r7yDc9psvg4yK39NUu53cajr5P5WnYw2Pxw1VdQbX9vwkhkC1veBRyh3e+2LUDtQ0g+4Ph2b0Z1UOlpVnX6yNp3tND5EkBQX4eC/xks7jgHXaz557XQ==&sign_type=RSA_1_256&sub_appid=wx80f7a21fbbe7980c&sub_openid=oUCV765SRSydZ2PMlmMzOKvvjv6M&total_fee=1&version=2.0";
+//    	String platPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlxmhbZBDvQt4ZVLXyAU8JMyvu0sNgdft4qAdS+c+spN1u1sewQf7d7CuMxFZqaprJboAUaCJN/7V/4wYBerDzXzqXzPuGvFV00GDYtRezVe8VJBfgpvmaNK8cq2pqaUVAxPFJJ9Nl/YTc4gaD38m+CYpopRP70Fgrq7BT4eGuak5Rpb50ICQ0yopex3yV8zS99EkgNeeI66K6Xgp3oQKs8YP6oig5NJrhc2Ct+aXxuYUHUzdDByetXtXYJmWlx5SBRfYYHWt/L6w8XUb3EUTUob9wLpMgbWJGDWFqXrSQagjKVo7vzcHXL4LsbIK7OPO3k7oOI8aPdgLbPCP0/cjkQIDAQAB";
+//    	String sign = "B+CxGl13bnD1yJylWJI/uJNN9Xd0TZ1GR0eQdXL9BByzIWCU4wJofmJfVRTTygo9Asisi1ey0BTwwtLsNJr1uXBKLvodEKp8rAxgm8hRAmy82F6KjjDQtQ4VORDWGHtoAECs2RNcSnsXUtggVj5lO4o0DceYuyPbnGPDI/oCHNbDt9LNQ0FMRKP0LbOFVwaa064qYTyI70xXsl2U/Rh2z5zjIf5CT06evL/r7yDc9psvg4yK39NUu53cajr5P5WnYw2Pxw1VdQbX9vwkhkC1veBRyh3e+2LUDtQ0g+4Ph2b0Z1UOlpVnX6yNp3tND5EkBQX4eC/xks7jgHXaz557XQ==";
+//    	System.out.println(verifySign(preStr, sign, "RSA_1_256", platPublicKey));
+	}
+}

+ 134 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/SignUtils.java

@@ -0,0 +1,134 @@
+/**
+ * Project Name:payment
+ * File Name:SignUtils.java
+ * Package Name:cn.swiftpass.utils.payment.sign
+ * Date:2014-6-27下午3:22:33
+ *
+*/
+
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.xml.sax.InputSource;
+
+import java.io.StringReader;
+import java.net.URLEncoder;
+import java.util.*;
+
+
+/**
+ * ClassName:SignUtils
+ * Function: 签名用的工具箱
+ * Date:     2014-6-27 下午3:22:33 
+ * @author    
+ */
+public class SignUtils {
+
+    /** <一句话功能简述>
+     * <功能详细描述>验证返回参数
+     * @param params
+     * @param key
+     * @return
+     * @see [类、类#方法、类#成员]
+     */
+    public static boolean checkParam(Map<String,String> params,String key){
+        boolean result = false;
+        if(params.containsKey("sign")){
+            String sign = params.get("sign");
+            params.remove("sign");
+            StringBuilder buf = new StringBuilder((params.size() +1) * 10);
+            SignUtils.buildPayParams(buf,params,false);
+            String preStr = buf.toString();
+            String signRecieve = MD5.sign(preStr, "&key=" + key, "utf-8");
+            result = sign.equalsIgnoreCase(signRecieve);
+        }
+        return result;
+    }
+    
+    /**
+     * 过滤参数
+     * @author  
+     * @param sArray
+     * @return
+     */
+    public static Map<String, String> paraFilter(Map<String, String> sArray) {
+        Map<String, String> result = new HashMap<String, String>(sArray.size());
+        if (sArray == null || sArray.size() <= 0) {
+            return result;
+        }
+        for (String key : sArray.keySet()) {
+            String value = sArray.get(key);
+            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
+                continue;
+            }
+            result.put(key, value);
+        }
+        return result;
+    }
+    
+    /** <一句话功能简述>
+     * <功能详细描述>将map转成String
+     * @param payParams
+     * @return
+     * @see [类、类#方法、类#成员]
+     */
+    public static String payParamsToString(Map<String, String> payParams){
+        return payParamsToString(payParams,false);
+    }
+    
+    public static String payParamsToString(Map<String, String> payParams,boolean encoding){
+        return payParamsToString(new StringBuilder(),payParams,encoding);
+    }
+    /**
+     * @author 
+     * @param payParams
+     * @return
+     */
+    public static String payParamsToString(StringBuilder sb,Map<String, String> payParams,boolean encoding){
+        buildPayParams(sb,payParams,encoding);
+        return sb.toString();
+    }
+    
+    /**
+     * @author 
+     * @param payParams
+     * @return
+     */
+    public static void buildPayParams(StringBuilder sb,Map<String, String> payParams,boolean encoding){
+        List<String> keys = new ArrayList<String>(payParams.keySet());
+        Collections.sort(keys);
+        for(String key : keys){
+            sb.append(key).append("=");
+            if(encoding){
+                sb.append(urlEncode(payParams.get(key)));
+            }else{
+                sb.append(payParams.get(key));
+            }
+            sb.append("&");
+        }
+        sb.setLength(sb.length() - 1);
+    }
+    
+    public static String urlEncode(String str){
+        try {
+            return URLEncoder.encode(str, "UTF-8");
+        } catch (Throwable e) {
+            return str;
+        } 
+    }
+    
+    
+    public static Element readerXml(String body,String encode) throws DocumentException {
+        SAXReader reader = new SAXReader(false);
+        InputSource source = new InputSource(new StringReader(body));
+        source.setEncoding(encode);
+        Document doc = reader.read(source);
+        Element element = doc.getRootElement();
+        return element;
+    }
+
+}
+

+ 158 - 0
src/main/java/com/zsElectric/boot/core/pay/swiftpass/util/XmlUtils.java

@@ -0,0 +1,158 @@
+/**
+ * Project Name:pay-protocol
+ * File Name:Xml.java
+ * Package Name:cn.swiftpass.pay.protocol
+ * Date:2014-8-10下午10:48:21
+ *
+*/
+
+package com.zsElectric.boot.core.pay.swiftpass.util;
+
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.xml.sax.InputSource;
+import java.io.*;
+import java.util.*;
+
+/**
+ * ClassName:Xml
+ * Function: XML的工具方法
+ * Date:     2014-8-10 下午10:48:21 
+ * @author    
+ */
+@SuppressWarnings({"rawtypes","unchecked"})
+public class XmlUtils {
+    
+    /** <一句话功能简述>
+     * <功能详细描述>request转字符串
+     * @param request
+     * @return
+     * @see [类、类#方法、类#成员]
+     */
+    public static String parseRequst(HttpServletRequest request){
+        String body = "";
+        try {
+            ServletInputStream inputStream = request.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+            while(true){
+                String info = br.readLine();
+                if(info == null){
+                    break;
+                }
+                if(body == null || "".equals(body)){
+                    body = info;
+                }else{
+                    body += info;
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }            
+        return body;
+    }
+    
+    public static String parseXML(SortedMap<String, String> parameters) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("<xml>");
+        Set es = parameters.entrySet();
+        Iterator it = es.iterator();
+        while (it.hasNext()) {
+            Map.Entry entry = (Map.Entry)it.next();
+            String k = (String)entry.getKey();
+            String v = (String)entry.getValue();
+            if (null != v && !"".equals(v) && !"appkey".equals(k)) {
+                sb.append("<" + k + ">" + parameters.get(k) + "</" + k + ">\n");
+            }
+        }
+        sb.append("</xml>");
+        return sb.toString();
+    }
+
+    /**
+     * 从request中获得参数Map,并返回可读的Map
+     * 
+     * @param request
+     * @return
+     */
+    public static SortedMap getParameterMap(HttpServletRequest request) {
+        // 参数Map
+        Map properties = request.getParameterMap();
+        // 返回值Map
+        SortedMap returnMap = new TreeMap();
+        Iterator entries = properties.entrySet().iterator();
+        Map.Entry entry;
+        String name = "";
+        String value = "";
+        while (entries.hasNext()) {
+            entry = (Map.Entry) entries.next();
+            name = (String) entry.getKey();
+            Object valueObj = entry.getValue();
+            if(null == valueObj){
+                value = "";
+            }else if(valueObj instanceof String[]){
+                String[] values = (String[])valueObj;
+                for(int i=0;i<values.length;i++){
+                    value = values[i] + ",";
+                }
+                value = value.substring(0, value.length()-1);
+            }else{
+                value = valueObj.toString();
+            }
+            returnMap.put(name, value.trim());
+        }
+        returnMap.remove("method");
+        return returnMap;
+    }
+    
+    /**
+     * 转XMLmap
+     * @author  
+     * @param xmlBytes
+     * @param charset
+     * @return
+     * @throws Exception
+     */
+    public static Map<String, String> toMap(byte[] xmlBytes,String charset) throws Exception{
+        SAXReader reader = new SAXReader(false);
+        InputSource source = new InputSource(new ByteArrayInputStream(xmlBytes));
+        source.setEncoding(charset);
+        Document doc = reader.read(source);
+        Map<String, String> params = XmlUtils.toMap(doc.getRootElement());
+        return params;
+    }
+    
+    /**
+     * 转MAP
+     * @author  
+     * @param element
+     * @return
+     */
+    public static Map<String, String> toMap(Element element){
+        Map<String, String> rest = new HashMap<String, String>();
+        List<Element> els = element.elements();
+        for(Element el : els){
+            rest.put(el.getName().toLowerCase(), el.getText());
+        }
+        return rest;
+    }
+    
+    public static String toXml(Map<String, String> params){
+        StringBuilder buf = new StringBuilder();
+        List<String> keys = new ArrayList<String>(params.keySet());
+        Collections.sort(keys);
+        buf.append("<xml>");
+        for(String key : keys){
+            buf.append("<").append(key).append(">");
+            buf.append("<![CDATA[").append(params.get(key)).append("]]>");
+            buf.append("</").append(key).append(">\n");
+        }
+        buf.append("</xml>");
+        return buf.toString();
+    }
+}
+

+ 20 - 1
src/main/resources/application-prod.yml

@@ -280,8 +280,27 @@ captcha:
   # 验证码有效期(秒)
   expire-seconds: 120
 
-# 微信小程序配置
 wx:
+  #商户号
+  mch_id: 105520131602
+
+  #小程序appid
+  appid: wx9894a01b9e92c368
+  #appid密钥
+  secret: b1e83dbcf83af310c38c0a138739ddcf
+
+  #md5密钥
+  key: f5131b3f07acb965a59041b690a29911
+  #同一支付请求地址
+  req_url: https://eoap.cebbank.com/uiap/bcac/pay/pay/gateway
+  #支付请求成功后的回调地址--需要配置正确的域名
+  #  notify_url: http://120.78.228.211:8880/orderApi/enditPay
+  notify_url: https://charge.hub.zswlgz.com/orderApi/orderNotify
+  #私钥
+  mchPrivateKey: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDeFB5G2OYT762PpUytCw7Du40i6WnzcmbvEE9IXPXi+QirPwMvW9mBqNDIUk5hQS3ZnHjj80YQRWG6yksjE6kHAYIWahCDiaPlBqYvYSJ8ePzbT61THZJbzqFaIG3svW7xq9nsUmzVBub0ATIzC1DQRu9ZTdrj/iuMUEhJyJ8IHrTP09eTwNYdoagHQlKWRVoNE3LuU4GXG3VCbkQ2ixbMo8dXBisDIi3GYOSFWzota6H+OCp9Mta1jTqdwALKAU9PNlRkQwOMLk2OmMqGUhImVVpl+eGrIYn3iARce0alNFg8hghFMJ8MKpSxJDM6YHNOJQ06S25YYhTpd+C2/VOBAgMBAAECggEBAM/W9ksJ/bJU0xOn+W3N9oB7C+jLmMwtmmZM1lZ8IefNeC6Ep59wD81ISDXiydY9YQLTbVSxPjZGKOPfJZjrcnrLD4uYsmHYtFnI8klPWC40MTmzhRxPhcWESgAGb7prw+RMGIUS0yY/8nAUmn2pLnXunVzv/1b3bpxAGpdrOmMmU28GBt9AlXiIpVmnxnkhp66c4zFj5gvvVoDrz9m/6Acyn6n7yccCHD2iYw0D78GPItEWky38tH/FV0lRcCCYAf5fc2nFnicrdgj1RYjqTWxM7A92UecviTAbiuPO6CQQl7+sMtGU2d6UeKj0Xrrl2gly+lOS97P/NcLtZf5vklECgYEA8SMlCg5AToSkIwal7dXYgM2VlNFwSRDuO3hEVoWe7bM/LkEp8dqSpV025ZtngTY/qziXsGP/7l8bcS1th19cX/+/MFFOWsoxtqvOam2Elp+Qg1johEfnWI+Bo4WiQ5CHYNQRN3cuiWTc5HHuI0KQAEPx/aYogD057X2FIsiu6S0CgYEA68RAFsupZ6R8BhJbfY2CK07XLNvXu5DAYBmU3trmQFnaxXeQFfhQ8hi9m5Awu14YCnRmHzc8+QXFD/GqTAbxtImc6AZQKWdLuUntkitPWmJK6dtJC8Is3U9Yqz5+CkSmgZSfqW0DvEt7jagoNfpKgy29Qq4r35b6JsDXtnTQICUCgYEAjmwnkEzihn2pRFbE4jiP62OBmagqHb22N8HM+x1oxRQ9mOA8GfDy9GCd7/ddpt+Xs1V1omUt4GikGLCwJGiacsjm727WTKFnw3CuNgYBbcVI4Ys9qgOeDJyWATMIp8dRbktS7+OgxN2h6fuwn3rM+psm7p2ZBkUjVbXxUJ4fUPECgYEAw1wmAv2VjRUF0/4oI5w7bWlx8XDljT1/uuHXsuZN/qq2FgRht2LAqCsKCjprtwZcA2W6LUmXU32Ncg29ICxs4j1ZcAWzLOu0GoAAxKrwoSNrkeYr2/t1M5kJDzTEOfvywNMHjduQSdl+Mr5RO5D/Zz1iYztxjV9MPwpydHTM9KUCgYAHuT98NkBilMxQNNmUB13E10MYQvZuiGVFZtT3up69Elpmtm7Z5cEW7QNG0g1LPPfkzfWPsq+6I98FmozLickqvjntdpul4czTITn8SNHqoxvdbcVnDipF1KwlHcnXhjO1KjSZg3a/iv554OR3/rbD9SWDzDAT7Zy6zX9n8OwGRA==
+  #公钥(商户平台的公钥,不是自己生成的公钥)
+  platPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2HOacYJOO9zsWPVmauV/YCeR78RsRDtusdLEngi/JkPkZVSE0X47z2RpJncGyV1QfdHv0udVEND4bvjXku4qUJp5DYAulm6pDXdcwWPcdI77V7dqoDvYm9Cc8kGj9s+/0xeuxX4qJmwzFTf7XjRfTT7+OVSvFnnButAkgMuD3cW1rtcQYeY9S9puQneN1i1+Lek5GCpW5PFsezK6QMgrpB1TFVSF5tloUODfc4fBDY5quGxn29Fo9gzJXO8ehoRft/JEaS4rNqmlfbvaJfEROXALlKoUX8Iki050ss7WwIBS6xuV08JnHTUHzHmAzOscwyYmT3RZChPgluWuyYW30wIDAQAB
+  # 微信小程序配置
   miniapp:
     # 微信小程序 AppID(在微信公众平台获取)
     app-id: wx9894a01b9e92c368