Prechádzať zdrojové kódy

fix: 对接保险接口

luofeiyun 2 týždňov pred
rodič
commit
6c3852ef43
19 zmenil súbory, kde vykonal 860 pridanie a 234 odobranie
  1. 18 22
      ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuranceApplyReqDTO.java
  2. 195 0
      ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuranceOrderInfoDTO.java
  3. 71 5
      ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuredDTO.java
  4. 3 2
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/api/insurance/InsuranceApiImpl.java
  5. 5 3
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/insurance/InsuranceController.java
  6. 32 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/notify/NotifyController.java
  7. 39 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/notify/vo/NotifyInsuranceReqVO.java
  8. 3 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeVisitorRespVO.java
  9. 5 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/mysql/insurance/InsuranceMapper.java
  10. 3 1
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/insurance/InsuranceService.java
  11. 131 189
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/insurance/InsuranceServiceImpl.java
  12. 16 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/notify/NotifyService.java
  13. 38 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/notify/NotifyServiceImpl.java
  14. 10 10
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/handler/InsuranceHandler.java
  15. 185 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/InsuranceUtil.java
  16. 99 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/SignUtil.java
  17. 2 1
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml
  18. 3 1
      ship-server-web/src/main/resources/application-sxtest.yaml
  19. 2 0
      ship-server-web/src/main/resources/application.yaml

+ 18 - 22
ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuranceApplyReqDTO.java

@@ -1,43 +1,39 @@
 package com.yc.ship.module.trade.api.insurance.dto;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.math.BigDecimal;
-import java.util.Date;
 import java.util.List;
 
 @Schema(description = "RPC 服务 - Insurance 投保 Request DTO")
 @Data
 public class InsuranceApplyReqDTO {
 
-    @Schema(description = "订单ID", example = "3807")
-    private Long orderId;
+    /** 服务类型 */
+    private String service;
 
-    @Schema(description = "旅行社订单ID", example = "3807")
-    private Long agencyOrderId;
+    /** 回调URL */
+    private String replyUrl;
 
-    @Schema(description = "保险产品代码")
-    private String rationType;
+    /** 在线支付标识 */
+    private String onlinePayInd;
 
-    @Schema(description = "游玩日期")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private Date travelDate;
+    /** 设备类型 */
+    private Integer deviceType;
 
-    @Schema(description = "保费")
-    private BigDecimal premium;
+    /** 状态回调URL */
+    private String statusReplyUrl;
 
-    @Schema(description = "保单保额")
-    private BigDecimal amount;
+    /** 前端回调URL */
+    private String frontReplyUrl;
 
-    @Schema(description = "投保人姓名", example = "李四")
-    private String holderName;
+    /** 回调全信息标志 */
+    private Integer callBackFullInfoFlag;
 
-    @Schema(description = "投保人证件号码")
-    private String holderNo;
+    /** 订单信息 */
+    private InsuranceOrderInfoDTO order;
 
-    @Schema(description = "被保人信息")
-    private List<InsuredDTO> insuredList;
+    /** 被保险人信息列表 */
+    private List<InsuredDTO> insureds;
 
 }

+ 195 - 0
ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuranceOrderInfoDTO.java

@@ -0,0 +1,195 @@
+package com.yc.ship.module.trade.api.insurance.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Schema(description = "RPC 服务 - Insurance 投保订单信息 Request DTO")
+@Data
+public class InsuranceOrderInfoDTO {
+
+    /** 必填 - 字符串 - 贵方的订单号 */
+    @Schema(description = "贵方的订单号")
+    @NotBlank
+    private String externalOrderNo;
+
+    /** 团保必填 - 字符串 - 贵方系统中的投保单号,必须唯一 */
+    @Schema(description = "贵方系统中的投保单号,必须唯一")
+    private String externalPolicyNumber;
+
+    /** 可选 - 字符串 - 团号 */
+    @Schema(description = "团队代码")
+    private String teamCode;
+
+    /** 可选 - 字符串 - 出行路线 */
+    @Schema(description = "航线")
+    private String line;
+
+    /** 可选 - 字符串 - 目的地所属洲 */
+    @Schema(description = "大洲")
+    private String continent;
+
+    /** 可选 - 字符串 - 目的地所属国家 */
+    @Schema(description = "国家")
+    private String nation;
+
+    /** 可选 - 字符串 - 目的地所属区域 */
+    @Schema(description = "地区")
+    private String area;
+
+    /** 可选 - 字符串 - 目的地州或省 */
+    @Schema(description = "州/省")
+    private String state;
+
+    /** 可选 - 字符串 - 目的地所属城市 */
+    @Schema(description = "目的地")
+    private String destination;
+
+    /** 可选 - 字符串 - 出发地城市 */
+    @Schema(description = "城市名称")
+    private String cityName;
+
+    /** 必填 - 字符串 - 产品代码, 由阳光指定 */
+    @Schema(description = "产品代码, 由阳光指定")
+    private String productNo;
+
+    /** 必填 - 字符串 - 起保日期时间 yyyy-MM-dd */
+    @Schema(description = "开始日期")
+    private String beginDate;
+
+    /** 必填 - 字符串 - 止保日期时间 yyyy-MM-dd */
+    @Schema(description = "结束日期")
+    private String endDate;
+
+    /** 在线支付必填, 且必须与阳光计算的待支付金额相等  */
+    @Schema(description = "费用")
+    private String cost;
+
+    /** 必填 - 字符串 - 价格单位,目前固定为CNY */
+    @Schema(description = "货币代码")
+    private String currencyCode;
+
+    /** 必填 - 数字 - 业务方式,非O2O为0,O2O为1; */
+    @Schema(description = "业务类型")
+    private Integer bizType;
+
+    /** 必填 - 数字 - 投保人类别, 值有0/1/2, 详情见“注释1” 团保时,此值只能为1 */
+    @Schema(description = "投保人类型")
+    private Integer applicantType;
+
+    /** 必填 - 字符串 - 投保人姓名 */
+    @Schema(description = "投保人姓名")
+    private String applicant;
+
+    /** 可选 - 字符串 - 投保人姓拼音 */
+    @Schema(description = "投保人名字")
+    private String applicantFirstName;
+
+    /** 可选 - 字符串 - 投保人名拼音 */
+    @Schema(description = "投保人姓氏")
+    private String applicantLastName;
+
+    /** 必填 - 数字 - 投保人证件类型
+     1	身份证
+     2	军人证
+     3	护照
+     4	学生证
+     5	港澳通行证
+     6	其他
+     */
+    @Schema(description = "投保人证件类型")
+    private Integer applicantCertificateType;
+
+    /** 必填 - 字符串 - 投保人证件号码 */
+    @Schema(description = "投保人证件号码")
+    private String applicantCertificateNo;
+
+    /** 必填 - 字符串 - 投保人生日 yyyy-MM-dd  */
+    @Schema(description = "投保人出生日期")
+    private String applicantBirthday;
+
+    /** 可选 - 数字 - 投保人性别 */
+    @Schema(description = "投保人性别")
+    private Integer applicantSex;
+
+    /** 可选 - 字符串 - 投保人手机  */
+    @Schema(description = "投保人手机号")
+    private String applicantMobile;
+
+    /** 可选 - 字符串 - 投保人邮箱(上海太保 - 必填) */
+    @Schema(description = "投保人邮箱")
+    private String applicantEmail;
+
+    /** 可选 - 字符串 - 投保人固定电话 */
+    @Schema(description = "投保人电话")
+    private String applicantPhone;
+
+    /** 可选 - 字符串 - 投保人联系地址 */
+    @Schema(description = "投保人地址")
+    private String applicantAddress;
+
+    /** 可选 - 字符串 - 投保人联系邮编 */
+    @Schema(description = "投保人邮编")
+    private String applicantZip;
+
+    /** 可选 - 数字 - 如上投保人与被保人的关系
+     * 0	是保单持有人,非被保险人
+     * 1	是保单持有人,且是被保险人
+     * 2	不是保单持有人,只是被保险人
+     * */
+    @Schema(description = "关系")
+    private Integer relation;
+
+    /** 必填 - 字符串 - 销售公司 */
+    @Schema(description = "销售公司")
+    private String sellCorp;
+
+    /** 必填 - 字符串 - 销售部门 */
+    @Schema(description = "销售部门")
+    private String sellDept;
+
+    /** 必填 - 字符串 - 销售人员姓名 */
+    @Schema(description = "销售人员")
+    private String sellUser;
+
+    /** 可选 - 字符串 - 销售人员编号 */
+    @Schema(description = "销售代码")
+    private String sellCode;
+
+    /** 可选 - 字符串 - 紧急联系人 */
+    @Schema(description = "紧急联系人")
+    private String emergencyContact;
+
+    /** 可选 - 字符串 - 紧急联系人电话 */
+    @Schema(description = "紧急联系电话")
+    private String emergencyPhone;
+
+    /** 可选 - 字符串 - 邮寄地址,比如发票邮寄 */
+    @Schema(description = "信函地址")
+    private String letterAddress;
+
+    /** 信函邮编 */
+    @Schema(description = "信函邮编")
+    private String letterZip;
+
+    /** 航班号 */
+    @Schema(description = "航班号")
+    private String flightNo;
+
+    /** 出发机场 */
+    @Schema(description = "出发机场")
+    private String flightOriginAirport;
+
+    /** 到达机场 */
+    @Schema(description = "到达机场")
+    private String flightArriveAirport;
+
+    /** 航班出发日期 */
+    @Schema(description = "航班出发日期")
+    private String flightBeginDate;
+
+    /** 航班到达日期 */
+    @Schema(description = "航班到达日期")
+    private String flightArriveDate;
+}

+ 71 - 5
ship-module-trade/ship-module-trade-api/src/main/java/com/yc/ship/module/trade/api/insurance/dto/InsuredDTO.java

@@ -5,12 +5,78 @@ import lombok.Data;
 @Schema(description = "RPC 服务 - Insurance 被保人 Request DTO")
 @Data
 public class InsuredDTO {
-    @Schema(description = "被保人姓名(游客名)")
+    /**
+     *  //必填 - 字符串 - 与被保人一一对应, 且必须唯一
+     *   //个保时,用于唯一标识被保人,也作投保单号,需要贵方系统上保持唯一.
+     *   //团保时,仅用于在当前请求中唯一标识被保人,不作为投保单号;
+     */
+    @Schema(description = "被保人唯一编号")
+    private String externalPolicyNumber;
+
+    /**
+     * 必填 - 字符串 - 被保险人姓名
+     */
+    @Schema(description = "被保人姓名")
     private String insuredName;
 
-    @Schema(description = "被保人证件号")
-    private String insuredIdNo;
+    /** 可选 - 字符串 - 被保险人姓拼音 */
+    private String firstName;
+
+    /** /可选 - 字符串 - 被保险人名拼音 */
+    private String lastName;
+
+    /** 必填 - 数字 - 被保险人证件类型 */
+    private Integer certificateType;
+
+    /** 必填 - 字符串 - 被保险人证件号码 */
+    private String certificateNo;
+
+    /** 必填 - 字符串 - 被保险人生日 yyyy-MM-dd */
+    private String birthday;
+
+    /** 必填 - 数字 - 被保险人性别
+     * 0	女
+     * 1	男
+     * */
+    private Integer sex;
+
+    /** 可选 - 字符串 - 被保险人手机号码(O2O业务时,必填) */
+    private String mobile;
+
+    /** 可选 - 字符串 - 被保险人邮箱(有些产品要求投保人时需要邮箱,具体见产品规则) */
+    private String email;
+
+    /** 可选 - 字符串 - 被保险人家庭住址(家财险时,必填) */
+    private String address;
+
+    /** 必填 - 数字 - 与投保人关系 */
+    private Integer relation;
+
+    //<!--applicantType为0且对应被保险人为未成年人时必填 - 开始-->
+    /** 必填 - 字符串 - 投保人姓名; */
+    private String applicant;
+
+    /** 可选 - 字符串 - 投保人姓拼音 */
+    private String applicantFirstName;
+
+    /** 可选 - 字符串 - 投保人名拼音  */
+    private String applicantLastName;
+
+    /** 必填 - 数字 - 投保人证件类型  */
+    private Integer applicantCertificateType;
+
+    /** 必填 - 字符串 - 投保人证件号码 */
+    private String applicantCertificateNo;
+
+    /** 必填 - 字符串 - 投保人生日 yyyy-MM-dd  */
+    private String applicantBirthday;
+
+    /** 可选 - 数字 - 投保人性别 */
+    private Integer applicantSex;
+
+    /** 可选 - 字符串 - 投保人手机   */
+    private String applicantMobile;
 
-    @Schema(description = "被保人证件类型")
-    private String insuredIdType;
+    /** 可选 - 字符串 - 投保人邮箱(上海太保 - 必填) */
+    private String applicantEmail;
 }

+ 3 - 2
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/api/insurance/InsuranceApiImpl.java

@@ -22,8 +22,9 @@ public class InsuranceApiImpl implements InsuranceApi {
 
     @Override
     public CommonResult<InsuredRespDTO> applyInsurance(InsuranceApplyReqDTO reqDTO) {
-
-        return insuranceService.applyInsurance(reqDTO);
+        //TODO: 待实现
+        return null;
+//        return insuranceService.applyInsurance(reqDTO);
     }
 
     @Override

+ 5 - 3
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/insurance/InsuranceController.java

@@ -69,15 +69,17 @@ public class InsuranceController {
                         list);
     }
 
-    @PostMapping("/applyInsurance")
+    @PutMapping("/applyInsurance")
     @Operation(summary = "申请投保")
     @OperateLog(type = API)
     @PreAuthorize("@ss.hasPermission('trade:insurance:apply')")
-    public CommonResult<Boolean> applyInsurance(@RequestBody InsuranceApplyReqDTO reqDTO) {
-        insuranceService.applyInsurance(reqDTO);
+    public CommonResult<Boolean> applyInsurance(@RequestParam("orderId") Long orderId) {
+        insuranceService.applyInsurance(orderId);
         return success(true);
     }
 
+    @PostMapping("/notif")
+
     @GetMapping("/queryEpolicy")
     @Operation(summary = "根据id获取电子保单")
     @OperateLog(type = API)

+ 32 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/notify/NotifyController.java

@@ -0,0 +1,32 @@
+package com.yc.ship.module.trade.controller.admin.notify;
+
+import com.alibaba.fastjson.JSONObject;
+import com.yc.ship.module.trade.controller.admin.notify.vo.NotifyInsuranceReqVO;
+import com.yc.ship.module.trade.service.notify.NotifyService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 第三方调用的通知接口
+ */
+@Tag(name = "管理后台 - 第三方调用的通知接口")
+@RestController
+@RequestMapping("/notify")
+@Slf4j
+public class NotifyController {
+
+    @Resource
+    private NotifyService notifyService;
+
+    @PostMapping("/insurance")
+    public Boolean insuranceNotify(@RequestBody NotifyInsuranceReqVO reqVO) {
+        log.info("收到保险通知:{}", JSONObject.toJSONString(reqVO));
+        return notifyService.notifyInsurance(reqVO);
+    }
+}

+ 39 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/notify/vo/NotifyInsuranceReqVO.java

@@ -0,0 +1,39 @@
+package com.yc.ship.module.trade.controller.admin.notify.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "订单保险通知")
+@Data
+public class NotifyInsuranceReqVO {
+
+    @Schema(description = "阶段")
+    private String phase;
+
+    @Schema(description = "服务类型, 值有apply, applyTeam")
+    private String service;
+
+    @Schema(description = "贵方的订单号, 就是订单号")
+    private String externalOrderNo;
+
+    @Schema(description = "贵方的投保单号,就是订单ID")
+    private String externalPolicyNumber;
+
+    @Schema(description = "状态, 值有SUCCESS, FAIL")
+    private String status;
+
+    @Schema(description = "错误信息")
+    private String msg;
+
+    @Schema(description = "签名, 加密方式见 附录 - 通用加密规则")
+    private String cipher;
+
+    @Schema(description = "保单号")
+    private String policyNo;
+
+    @Schema(description = "保单下载地址")
+    private String downloadUrl;
+
+    @Schema(description = "成功时间")
+    private String successDate;
+}

+ 3 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeVisitorRespVO.java

@@ -72,4 +72,7 @@ public class TradeVisitorRespVO {
     @Schema(description = "房间ID")
     private Long roomId;
 
+    @Schema(description = "生日")
+    private String birthday;
+
 }

+ 5 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/mysql/insurance/InsuranceMapper.java

@@ -1,5 +1,6 @@
 package com.yc.ship.module.trade.dal.mysql.insurance;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.yc.ship.framework.common.pojo.PageResult;
@@ -46,4 +47,8 @@ public interface InsuranceMapper extends BaseMapperX<InsuranceDO> {
         updateWrapper.set(InsuranceDO::getOrderId, mainOrderId);
         update(updateWrapper);
     }
+
+    default InsuranceDO selectByOrderId(Long orderId) {
+        return selectOne(new LambdaQueryWrapper<InsuranceDO>().eq(InsuranceDO::getOrderId, orderId).last("limit 1"));
+    }
 }

+ 3 - 1
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/insurance/InsuranceService.java

@@ -49,7 +49,7 @@ public interface InsuranceService {
      *
      * @return
      */
-    CommonResult<InsuredRespDTO> applyInsurance(InsuranceApplyReqDTO applyReqVO);
+    void applyInsurance(Long orderId);
 
     /**
      * 取消投保
@@ -61,4 +61,6 @@ public interface InsuranceService {
      */
     void queryEpolicyList(Long id);
     void queryEpolicyAll();
+
+    InsuranceDO getByOrderId(Long orderId);
 }

+ 131 - 189
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/insurance/InsuranceServiceImpl.java

@@ -10,28 +10,40 @@ import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.yc.ship.framework.common.exception.ServiceException;
 import com.yc.ship.framework.common.pojo.CommonResult;
 import com.yc.ship.framework.common.pojo.PageResult;
 import com.yc.ship.framework.mybatis.core.query.LambdaQueryWrapperX;
 import com.yc.ship.framework.mybatis.core.util.MyBatisUtils;
 import com.yc.ship.module.infra.api.config.ConfigApi;
+import com.yc.ship.module.product.dal.dataobject.voyage.VoyageDO;
+import com.yc.ship.module.product.service.voyage.VoyageService;
 import com.yc.ship.module.trade.api.insurance.dto.InsuranceApplyReqDTO;
+import com.yc.ship.module.trade.api.insurance.dto.InsuranceOrderInfoDTO;
 import com.yc.ship.module.trade.api.insurance.dto.InsuredDTO;
 import com.yc.ship.module.trade.api.insurance.dto.InsuredRespDTO;
 import com.yc.ship.module.trade.controller.admin.insurance.vo.HccResult;
 import com.yc.ship.module.trade.controller.admin.insurance.vo.InsuranceData;
 import com.yc.ship.module.trade.controller.admin.insurance.vo.InsurancePageReqVO;
 import com.yc.ship.module.trade.controller.admin.insurance.vo.InsuranceRespVO;
+import com.yc.ship.module.trade.controller.admin.order.vo.order.TradeOrderRespVO;
+import com.yc.ship.module.trade.controller.admin.order.vo.order.TradeVisitorRespVO;
 import com.yc.ship.module.trade.dal.dataobject.insurance.InsuranceDO;
 import com.yc.ship.module.trade.dal.mysql.insurance.InsuranceMapper;
+import com.yc.ship.module.trade.dal.mysql.order.TradeOrderMapper;
+import com.yc.ship.module.trade.dal.mysql.order.TradeVisitorMapper;
 import com.yc.ship.module.trade.enums.CardTypeEnum;
+import com.yc.ship.module.trade.enums.CredentialTypeEnum;
 import com.yc.ship.module.trade.enums.InsuranceStatusEnum;
+import com.yc.ship.module.trade.service.order.TradeOrderService;
 import com.yc.ship.module.trade.utils.CommonUUCodeUtils;
 import com.yc.ship.module.trade.utils.InsuranceRequestHelper;
+import com.yc.ship.module.trade.utils.InsuranceUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
@@ -47,6 +59,7 @@ import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception0;
 import static com.yc.ship.module.trade.enums.ErrorCodeConstants.*;
 
 
@@ -71,6 +84,21 @@ public class InsuranceServiceImpl implements InsuranceService {
     @Resource
     private RedissonClient redissonClient;
 
+    @Resource
+    private TradeOrderMapper tradeOrderMapper;
+
+    @Resource
+    private TradeVisitorMapper tradeVisitorMapper;
+
+    @Resource
+    private VoyageService voyageService;
+
+    @Resource
+    private InsuranceUtil insuranceUtil;
+
+    @Value("${yudao.notify.insurance}")
+    private String notifyUrl;
+
 
     private void validateInsuranceExists(Long id) {
         if (insuranceMapper.selectById(id) == null) {
@@ -107,86 +135,106 @@ public class InsuranceServiceImpl implements InsuranceService {
      */
     @Override
     @Transactional
-    public CommonResult<InsuredRespDTO> applyInsurance(InsuranceApplyReqDTO applyReqVO) {
-        InsuredRespDTO respDTO = new InsuredRespDTO();
-        try {
-            String riskCode = configApi.getConfigValueByKey("insurance.riskCode");
-            InsuranceDO insuranceDO = null;
-            //保存投保信息
-            insuranceDO = new InsuranceDO();
-            Long orderId = applyReqVO.getOrderId();
-            insuranceDO.setOrderId(orderId);
-            insuranceDO.setAgencyOrderId(applyReqVO.getAgencyOrderId());
-            insuranceDO.setInsuranceStatus(InsuranceStatusEnum.FAIL.getValue());
-            insuranceDO.setInsuredNum(applyReqVO.getInsuredList().size());
-            insuranceDO.setHolderName(applyReqVO.getHolderName());
-            insuranceDO.setHolderNo(applyReqVO.getHolderNo());
-            insuranceDO.setRationType(applyReqVO.getRationType());
-            insuranceDO.setRiskCode(riskCode);
-            insuranceDO.setInsuranceEffectDate(LocalDateTime.ofInstant(applyReqVO.getTravelDate().toInstant(), ZoneId.systemDefault()));
-            Long id = IdWorker.getId(insuranceDO);
-            insuranceDO.setId(id);
-            insuranceDO.setDeleted(false);
-            JSONObject reqObj = this.assembleInsuranceRequest(applyReqVO.getInsuredList(), applyReqVO.getRationType(), riskCode, applyReqVO.getTravelDate(),
-                    applyReqVO.getPremium(), applyReqVO.getAmount(), applyReqVO.getHolderName(), applyReqVO.getHolderNo(), orderId);
-            insuranceMapper.insert(insuranceDO);
-            String url = configApi.getConfigValueByKey("insurance.url");
+    public void applyInsurance(Long orderId) {
 
-            RLock lock = redissonClient.getLock(INSURANCE_KEY + orderId);
-            try {
-                if (lock.tryLock(30, 60, TimeUnit.SECONDS)) {
-                    log.error("电子保险请求参数:{}",url+";"+JSONUtil.toJsonStr(reqObj));
-                    String result = HttpUtil.createPost(url).contentType("application/json").body(JSONUtil.toJsonStr(reqObj)).timeout(15000).execute().body();
-                    log.error("电子保险返回参数:{}",result);
-                    JSONObject insuranceResponse = JSONUtil.parseObj(result);
-                    String publicKey = configApi.getConfigValueByKey("insurance.publicKey");
-                    String privateKey = configApi.getConfigValueByKey("insurance.privateKey");
-                    InsuranceRequestHelper.checkSignAndDecrypt(insuranceResponse, publicKey, privateKey);
-
-                    // 0:失败 1:成功
-                    if (null != insuranceResponse && "200".equals(insuranceResponse.get("code"))) {
-                        String data = insuranceResponse.get("data").toString();
-                        if (data != null) {
-                            InsuranceData insuranceData = JSONUtil.toBean(data, InsuranceData.class);
-                            insuranceDO.setResCode(insuranceData.getHccResults().get(0).getCode());
-                            insuranceDO.setResMsg( insuranceData.getHccResults().get(0).getMsg());
-                            if ("SUCCESS".equalsIgnoreCase(insuranceData.getHccResults().get(0).getCode())) {
-                                List<HccResult> hccResultDetailList = insuranceData.getHccResults();
-                                HccResult hccResultDetail = hccResultDetailList.get(0);
-                                insuranceDO.setInsuranceNo(hccResultDetail.getOrderNo());
-                                Integer insuranceStatus = StringUtils.equalsIgnoreCase("success", hccResultDetail.getStatus())
-                                        ? InsuranceStatusEnum.SUCCESS.getValue() : InsuranceStatusEnum.FAIL.getValue();
-                                insuranceDO.setInsuranceStatus(insuranceStatus);
-                                insuranceDO.setPolicyNo(hccResultDetail.getPolicyNo());
-                                insuranceDO.setProposalNo(hccResultDetail.getProposalNo());
-                                insuranceDO.setAmount(new BigDecimal(hccResultDetail.getAmount() + ""));
-                                insuranceDO.setPremium(new BigDecimal(hccResultDetail.getPremium() + ""));
-                                insuranceMapper.updateById(insuranceDO);
-                                respDTO.setInsuranceStatus(insuranceStatus);
-                                respDTO.setInsuredId(insuranceDO.getId());
-                                return CommonResult.success(respDTO);
-                            }
-                            insuranceMapper.updateById(insuranceDO);
-                        }
-                    }
-                } else {
-                    return CommonResult.error(INSURE_FAILED);
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                log.error("保险申报异常,:", e);
-                throw exception(CONTRACT_APPLY_FAILED);
-            } finally {
-                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
-                    lock.unlock();
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            log.error("[投保申请]:" + e.getMessage());
-            return CommonResult.error(INSURE_FAILED);
+        TradeOrderRespVO orderInfo = tradeOrderMapper.getOrderInfo(orderId);
+        VoyageDO voyage = voyageService.getVoyage(orderInfo.getVoyageId());
+
+        InsuranceApplyReqDTO insuranceApplyReqDTO = new InsuranceApplyReqDTO();
+        insuranceApplyReqDTO.setService("applyTeam");
+        insuranceApplyReqDTO.setReplyUrl(notifyUrl);
+        insuranceApplyReqDTO.setOnlinePayInd("0");
+
+        //订单对象, 一个订单下可以有一批被保险人
+        InsuranceOrderInfoDTO insuranceOrderInfoDTO = new InsuranceOrderInfoDTO();
+
+        //投保人信息
+        insuranceOrderInfoDTO.setExternalOrderNo(orderInfo.getOrderNo());
+        insuranceOrderInfoDTO.setExternalPolicyNumber(orderInfo.getId().toString());
+        insuranceOrderInfoDTO.setTeamCode(orderInfo.getGroupNo());
+        //TODO: 当前写死
+        insuranceOrderInfoDTO.setProductNo("SIGCOE_ZBJC1");
+        insuranceOrderInfoDTO.setBeginDate(DateUtil.format(voyage.getBoardingTime(), "yyyy-MM-dd"));
+        insuranceOrderInfoDTO.setEndDate(DateUtil.format(voyage.getLeaveTime(), "yyyy-MM-dd"));
+        insuranceOrderInfoDTO.setCurrencyCode("CNY");
+        insuranceOrderInfoDTO.setBizType(1);
+        insuranceOrderInfoDTO.setApplicantType(1);
+        insuranceOrderInfoDTO.setApplicant("宜昌长江国际旅行社有限公司");
+        insuranceOrderInfoDTO.setApplicantCertificateType(6);
+        insuranceOrderInfoDTO.setApplicantCertificateNo("91420500737133035K");
+        insuranceOrderInfoDTO.setApplicantBirthday("1990-01-01");
+        insuranceOrderInfoDTO.setApplicantSex(1);
+
+        //销售人信息
+        insuranceOrderInfoDTO.setSellCorp("测试公司");
+        insuranceOrderInfoDTO.setSellDept("测试部门");
+        insuranceOrderInfoDTO.setSellUser("测试人");
+        insuranceApplyReqDTO.setOrder(insuranceOrderInfoDTO);
+
+        //被保人对象列表, 其下可多位被保险人, 他们在同个订单下
+        List<TradeVisitorRespVO> tradeVisitorRespVOS = tradeVisitorMapper.queryVisitorByOrderId(orderId);
+        List<InsuredDTO> insuredList = new ArrayList<>();
+        tradeVisitorRespVOS.stream().forEach(tradeVisitorRespVO -> {
+            InsuredDTO insuredDTO = new InsuredDTO();
+            insuredDTO.setExternalPolicyNumber(tradeVisitorRespVO.getId().toString());
+            insuredDTO.setInsuredName(tradeVisitorRespVO.getName());
+            insuredDTO.setCertificateType(transCredentialType(tradeVisitorRespVO.getCredentialType()));
+            insuredDTO.setCertificateNo(tradeVisitorRespVO.getCredentialNo());
+            insuredDTO.setBirthday(tradeVisitorRespVO.getBirthday());
+            insuredDTO.setSex(tradeVisitorRespVO.getGender());
+            insuredDTO.setRelation(2); //不是保单持有人,只是被保险人
+            insuredList.add(insuredDTO);
+        });
+        insuranceApplyReqDTO.setInsureds(insuredList);
+
+        //验证投保信息
+//        CommonResult commonResult = insuranceUtil.validateInsuranceRequest(insuranceApplyReqDTO);
+//        if(!commonResult.isSuccess()) {
+//            throw exception0(commonResult.getCode(),commonResult.getMsg());
+//        }
+        // 开始投保
+        CommonResult commonResult = insuranceUtil.sendInsuranceApply(insuranceApplyReqDTO);
+        if(!commonResult.isSuccess()) {
+            throw exception0(commonResult.getCode(),commonResult.getMsg());
         }
-        return CommonResult.error(INSURE_FAILED);
+        //保存投保信息
+        InsuranceDO insuranceDO = new InsuranceDO();
+        insuranceDO.setOrderId(orderId);
+//            insuranceDO.setAgencyOrderId(applyReqVO.getAgencyOrderId());
+        insuranceDO.setInsuranceStatus(InsuranceStatusEnum.INSURE.getValue());
+        insuranceDO.setInsuredNum(insuredList.size());
+//            insuranceDO.setHolderName(applyReqVO.getHolderName());
+//            insuranceDO.setHolderNo(applyReqVO.getHolderNo());
+//            insuranceDO.setRationType(applyReqVO.getRationType());
+//            insuranceDO.setRiskCode(riskCode);
+//            insuranceDO.setInsuranceEffectDate(LocalDateTime.ofInstant(applyReqVO.getTravelDate().toInstant(), ZoneId.systemDefault()));
+        Long id = IdWorker.getId(insuranceDO);
+        insuranceDO.setId(id);
+        insuranceMapper.insert(insuranceDO);
+    }
+
+    /**
+     * 处理系统证件枚举与保险证件枚举转换
+     * @param credentialType
+     * @return
+     */
+    private Integer transCredentialType(Integer credentialType) {
+        Integer transCredentialType = 6; //默认为其他
+        switch (credentialType) {
+            case 0:
+                transCredentialType = 1;
+                break;
+            case 1:
+                transCredentialType = 3;
+                break;
+            case 2: case 3: case 4: case 6: case 7: case 8: case 9: case 99:
+                transCredentialType = 6;
+                break;
+            case 5:
+                transCredentialType = 5;
+                break;
+        }
+        return transCredentialType;
     }
 
     /**
@@ -291,6 +339,11 @@ public class InsuranceServiceImpl implements InsuranceService {
         }
     }
 
+    @Override
+    public InsuranceDO getByOrderId(Long orderId) {
+        return insuranceMapper.selectByOrderId(orderId);
+    }
+
     /**
      * 电子保单查询
      */
@@ -363,117 +416,6 @@ public class InsuranceServiceImpl implements InsuranceService {
         }
     }
 
-    /**
-     * 组装申请保险参数
-     *
-     * @param insuredList
-     * @param rationType  产品代码
-     * @param premium     保费
-     * @param amount      保额
-     */
-    private JSONObject assembleInsuranceRequest(List<InsuredDTO> insuredList, String rationType, String riskCode, Date travelDate,
-                                                BigDecimal premium, BigDecimal amount, String HolderName, String holderIdNo, Long orderDetailId) throws Exception {
-        JSONObject head = this.getCommonHead(rationType, riskCode, "INPUT");
-
-        JSONObject hccInsurance = new JSONObject();
-        //订单号
-        hccInsurance.set("orderNo", CommonUUCodeUtils.createInsuranceFlowNo());
-        //产品代码
-        hccInsurance.set("rationType", rationType);
-        //投保时间
-        hccInsurance.set("insurDate", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
-        String startDate = DateUtil.formatDate(travelDate) + " 00:00:00";
-        String endDate = DateUtil.formatDate(travelDate) + " 23:59:59";
-        //起保日期
-        hccInsurance.set("insurStartDate", startDate);
-        //终保日期
-        hccInsurance.set("insurEndDate", endDate);
-        //保险区间
-        hccInsurance.set("insurPeriod", "1");
-        //保险区间单位
-        hccInsurance.set("insurYearFlag", "D");
-        //购买份数
-        hccInsurance.set("mult", "1");
-        //保费   (测)
-        hccInsurance.set("premium",premium + "");
-        //保额
-        hccInsurance.set("amount", amount.multiply(new BigDecimal(insuredList.size())) + "");
-        hccInsurance.set("inputDate", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN));
-        hccInsurance.set("sysFlag", configApi.getConfigValueByKey("insurance.sysflag"));
-        //险种代码
-        hccInsurance.set("mainRiskCode", riskCode);
-
-        List<JSONObject> hccInsuredList = new ArrayList<>();
-
-        insuredList.forEach(insured -> {
-            JSONObject hccInsured = new JSONObject();
-            //被保人编号
-            hccInsured.set("insuredNo", orderDetailId);
-            //被保人姓名
-            hccInsured.set("insuredName", insured.getInsuredName());
-            //被保人证件类型
-            hccInsured.set("insuredIdType", CardTypeEnum.getInsuranceCardCodeById(insured.getInsuredIdType()));
-            //被保人证件号码
-            hccInsured.set("insuredIdNo", insured.getInsuredIdNo());
-            hccInsuredList.add(hccInsured);
-        });
-
-        //投保人信息
-        JSONObject hccHolderInfo = new JSONObject();
-        //投保人姓名
-        hccHolderInfo.set("holderName", HolderName);
-        //投保人证件号码
-        hccHolderInfo.set("holderIdNo", holderIdNo);
-        //投保人证件类型
-        //    01 身份证
-        //     * 02	户口薄
-        //     * 03	护照
-        //     * 04	军官证
-        //     * 05	驾驶执照
-        //     * 06	返乡证
-        //     * 99	其它
-        //     * 07	异常身份证
-        //     * 08	组织机构代码证
-        //     * 09	统一社会信用代码
-        //     * 48	港澳台居民居住证
-        //hccHolderInfo.set("holderIdType", "01");
-        String holderIdType = "01"; // 默认值
-        if (!insuredList.isEmpty()) {
-            boolean allNot01 = insuredList.stream()
-                    .map(InsuredDTO::getInsuredIdType)
-                    .allMatch(type -> !"01".equals(type));
-
-            if (allNot01) {
-                holderIdType = "03";
-            }
-        }
-        hccHolderInfo.set("holderIdType", holderIdType);
-        JSONObject insurancePolicy = new JSONObject();
-        insurancePolicy.set("hccInsurance", hccInsurance);
-        insurancePolicy.set("hccInsuredList", hccInsuredList);
-        insurancePolicy.set("hccHolderInfo", hccHolderInfo);
-
-        List<JSONObject> insurancePolicyList = new ArrayList();
-        insurancePolicyList.add(insurancePolicy);
-
-        JSONObject applyInsuranceRequest = new JSONObject();
-        applyInsuranceRequest.set("head", head);
-        applyInsuranceRequest.set("insurancePolicyList", insurancePolicyList);
-
-        JSONObject insuranceRequest = new JSONObject();
-        insuranceRequest.set("appKey", configApi.getConfigValueByKey("insurance.sysflag"));
-        insuranceRequest.set("timestamp", System.currentTimeMillis() + "");
-        insuranceRequest.set("serviceName", "insurance");
-        insuranceRequest.set("version", configApi.getConfigValueByKey("insurance.version"));
-        insuranceRequest.set("encryptType", ENCRYPT_TYPE);
-        insuranceRequest.set("bizContent", JSONUtil.toJsonStr(applyInsuranceRequest));
-        String publicKey = configApi.getConfigValueByKey("insurance.publicKey");
-        String privateKey = configApi.getConfigValueByKey("insurance.privateKey");
-        //加密加签
-        InsuranceRequestHelper.encryptAndSign(insuranceRequest, publicKey, privateKey);
-        return insuranceRequest;
-    }
-
     private JSONObject getCommonHead(String rationType, String riskCode, String type) {
         JSONObject head = new JSONObject();
         head.set("sysFlag", configApi.getConfigValueByKey("insurance.sysflag"));

+ 16 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/notify/NotifyService.java

@@ -0,0 +1,16 @@
+package com.yc.ship.module.trade.service.notify;
+
+import com.yc.ship.module.trade.controller.admin.notify.vo.NotifyInsuranceReqVO;
+
+/**
+ * 第三方调用的通知接口
+ */
+public interface NotifyService {
+
+    /**
+     * 保单投保结果通知
+     * @param reqVO
+     * @return
+     */
+    Boolean notifyInsurance(NotifyInsuranceReqVO reqVO);
+}

+ 38 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/notify/NotifyServiceImpl.java

@@ -0,0 +1,38 @@
+package com.yc.ship.module.trade.service.notify;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import com.yc.ship.module.trade.controller.admin.notify.vo.NotifyInsuranceReqVO;
+import com.yc.ship.module.trade.dal.dataobject.insurance.InsuranceDO;
+import com.yc.ship.module.trade.dal.mysql.insurance.InsuranceMapper;
+import com.yc.ship.module.trade.enums.InsuranceStatusEnum;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 第三方调用通知的具体处理业务
+ */
+@Service
+public class NotifyServiceImpl implements NotifyService {
+
+    @Resource
+    private InsuranceMapper insuranceMapper;
+    @Override
+    public Boolean notifyInsurance(NotifyInsuranceReqVO reqVO) {
+        String status = reqVO.getStatus();
+        String externalPolicyNumber = reqVO.getExternalPolicyNumber();
+        String policyNo = reqVO.getPolicyNo();
+        String downloadUrl = reqVO.getDownloadUrl();
+        String successDate = reqVO.getSuccessDate();
+        Long orderId = Long.valueOf(externalPolicyNumber);
+        if("SUCCESS".equals(status)) {
+            InsuranceDO insuranceDO = insuranceMapper.selectByOrderId(orderId);
+            insuranceDO.setPolicyNo(policyNo);
+            insuranceDO.setElectronicPolicy(downloadUrl);
+            insuranceDO.setInsuranceStatus(InsuranceStatusEnum.SUCCESS.getValue());
+            insuranceDO.setInsuranceEffectDate(LocalDateTimeUtil.parse(successDate));
+            insuranceMapper.updateById(insuranceDO);
+        }
+        return true;
+    }
+}

+ 10 - 10
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/handler/InsuranceHandler.java

@@ -73,14 +73,14 @@ public class InsuranceHandler implements TradeOrderHandler{
             }
             TradeVisitorBO holder = visitorDOList.get(0);
             InsuranceApplyReqDTO reqDTO = new InsuranceApplyReqDTO();
-            reqDTO.setOrderId(tradeOrderDO.getId());
-            reqDTO.setAmount(MapUtil.get(insuranceProduct,"insurance_amount", BigDecimal.class));
-            reqDTO.setPremium(MapUtil.get(insuranceProduct,"num", BigDecimal.class));
-            reqDTO.setRationType(MapUtil.get(insuranceProduct,"insurance_code", String.class));
-            reqDTO.setTravelDate(holder.getUseDate());
-            reqDTO.setHolderName(holder.getName());
-            reqDTO.setHolderNo(holder.getCredentialNo());
-            reqDTO.setInsuredList(CollectionUtils.convertList(visitorDOList, this::convert));
+//            reqDTO.setOrderId(tradeOrderDO.getId());
+//            reqDTO.setAmount(MapUtil.get(insuranceProduct,"insurance_amount", BigDecimal.class));
+//            reqDTO.setPremium(MapUtil.get(insuranceProduct,"num", BigDecimal.class));
+//            reqDTO.setRationType(MapUtil.get(insuranceProduct,"insurance_code", String.class));
+//            reqDTO.setTravelDate(holder.getUseDate());
+//            reqDTO.setHolderName(holder.getName());
+//            reqDTO.setHolderNo(holder.getCredentialNo());
+//            reqDTO.setInsuredList(CollectionUtils.convertList(visitorDOList, this::convert));
             dataList.add(reqDTO);
         }
 
@@ -114,9 +114,9 @@ public class InsuranceHandler implements TradeOrderHandler{
 
     private InsuredDTO convert(TradeVisitorBO tradeVisitorBO){
         InsuredDTO insuredDTO = new InsuredDTO();
-        insuredDTO.setInsuredIdNo(tradeVisitorBO.getCredentialNo());
+//        insuredDTO.setInsuredIdNo(tradeVisitorBO.getCredentialNo());
         insuredDTO.setInsuredName(tradeVisitorBO.getName());
-        insuredDTO.setInsuredIdType(String.valueOf(tradeVisitorBO.getCredentialType()));
+//        insuredDTO.setInsuredIdType(String.valueOf(tradeVisitorBO.getCredentialType()));
         return insuredDTO;
     }
 

+ 185 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/InsuranceUtil.java

@@ -0,0 +1,185 @@
+package com.yc.ship.module.trade.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.yc.ship.framework.common.pojo.CommonResult;
+import com.yc.ship.module.trade.api.insurance.dto.InsuranceApplyReqDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception0;
+
+/**
+ * 阳光保险接口工具类
+ */
+@Slf4j
+@Component
+public class InsuranceUtil {
+
+    public final String HOST = "https://lbb-admin.anyitech.ltd"; //测试域名
+
+    public final String VALIDATE_URL = "/policy/validateJson.do"; // 请求校验接口
+
+    public final String RECEIVE_URL = "/policy/receiveJson.do"; // JSON投/退保接口
+
+    public final String APPID = "123456";   // appId
+
+    public final String KEY = "goldpalm"; // key
+
+    private final Integer VALIDATE_LEVEL = 0; //校验级别,其值有 0/1
+
+    /** REST模板 */
+    private RestTemplate restTemplate;
+
+    /**
+     * 构造函数
+     */
+    public InsuranceUtil() {
+        this.restTemplate = new RestTemplate();
+    }
+
+
+    /**
+     * 发送投保请求
+     * 将投保请求发送到阳光系统
+     * @param request 投保请求
+     * @return 投保响应
+     */
+    public CommonResult sendInsuranceApply(InsuranceApplyReqDTO request) {
+        try {
+            String reqJson = JSONObject.toJSONString(request);
+            String sign = SignUtil.generateMD5Sign(APPID, reqJson, KEY);
+
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("appId", APPID);
+            params.add("req", reqJson);
+            params.add("sign", sign);
+
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+
+            HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
+
+            log.info("发送投保请求到阳光系统: {}", HOST + RECEIVE_URL);
+            log.info("请求参数: appId={}, req={}, sign={}", APPID, reqJson, sign);
+
+            // 发送请求并获取响应
+            ResponseEntity<byte[]> response = restTemplate.exchange(
+                    HOST + RECEIVE_URL,
+                    HttpMethod.POST,
+                    entity,
+                    byte[].class
+            );
+
+            log.info("阳光系统响应状态码: {}", response.getStatusCode());
+
+            // 手动处理响应字节流,确保使用UTF-8编码
+            byte[] responseBytes = response.getBody();
+            String responseBody = null;
+            if (responseBytes != null) {
+                try {
+                    responseBody = new String(responseBytes, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    log.error("解析响应内容编码失败", e);
+                    responseBody = new String(responseBytes);
+                }
+            }
+
+            log.info("阳光系统响应内容: {}", responseBody);
+
+            if (response.getStatusCode() == HttpStatus.OK) {
+                // 返回阳光系统的原始响应内容
+                return CommonResult.success(responseBody);
+            } else {
+                return CommonResult.error(500, "阳光系统返回错误: " + response.getStatusCode() + responseBody);
+            }
+
+        } catch (Exception e) {
+            log.error("发送投保请求失败", e);
+            return CommonResult.error(500,"发送投保请求失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 校验保险请求
+     * 在调用投保接口前,为了提高投保成功率,可先调用校验接口,检查客户系统组织的请求是否符合预期
+     * @param request 投保请求
+     * @return 校验响应
+     */
+    public CommonResult validateInsuranceRequest(InsuranceApplyReqDTO request) {
+        try {
+            String reqJson =  JSONObject.toJSONString(request);
+
+            // 构建校验请求参数
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("appId", APPID);
+            params.add("req", reqJson);
+            params.add("validateLevel", VALIDATE_LEVEL.toString());
+
+            // 生成签名
+            Map<String, String> signParams = new HashMap<>();
+            signParams.put("appId", APPID);
+            signParams.put("req", reqJson);
+            signParams.put("validateLevel", VALIDATE_LEVEL.toString());
+            String sign = SignUtil.generateMD5Sign(signParams, KEY);
+            params.add("sign", sign);
+
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+
+            HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
+
+            // 校验接口地址
+            String validateApiUrl = HOST + VALIDATE_URL;
+
+            log.info("发送校验请求到阳光系统: {}", validateApiUrl);
+            log.info("请求参数: appId={}, validateLevel={}, sign={}", APPID, VALIDATE_LEVEL, sign);
+
+            // 发送请求并获取响应
+            ResponseEntity<byte[]> response = restTemplate.exchange(
+                    validateApiUrl,
+                    HttpMethod.POST,
+                    entity,
+                    byte[].class
+            );
+
+            log.info("阳光系统校验响应状态码: {}", response.getStatusCode());
+            // 手动处理响应字节流,确保使用UTF-8编码
+            byte[] responseBytes = response.getBody();
+            String responseBody = null;
+            if (responseBytes != null) {
+                try {
+                    responseBody = new String(responseBytes, "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    log.error("解析响应内容编码失败", e);
+                    responseBody = new String(responseBytes);
+                }
+            }
+
+            log.info("阳光系统校验响应内容: {}", responseBody);
+            JSONObject responseBodyJson = JSONObject.parseObject(responseBody);
+            if ("200".equals(responseBodyJson.getString("code"))) {
+                JSONObject data = responseBodyJson.getJSONObject("data");
+                String status = data.getString("status");
+                if("SUCCESS".equals(status)) {
+                    return CommonResult.success(responseBody);
+                }else {
+                    return CommonResult.error(500, "阳光系统校验返回错误: " + response.getStatusCode() + responseBody);
+                }
+            } else {
+                // 响应失败,返回错误信息
+                return CommonResult.error(500, "阳光系统校验返回错误: " + response.getStatusCode() + responseBody);
+            }
+        } catch (Exception e) {
+            log.error("发送校验请求失败", e);
+            throw exception0(500,"发送校验请求失败: " + e.getMessage(), e);
+        }
+    }
+}

+ 99 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/SignUtil.java

@@ -0,0 +1,99 @@
+package com.yc.ship.module.trade.utils;
+
+import com.anji.captcha.util.MD5Util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * 签名工具类
+ * 用于生成阳光系统要求的MD5签名
+ */
+public class SignUtil {
+    
+    /** 字符集 */
+    private static final String CHARSET = "UTF-8";
+    
+    /**
+     * 生成MD5签名
+     * 用于生成阳光系统要求的MD5签名
+     * @param appId 应用ID
+     * @param req 请求数据
+     * @param key 密钥
+     * @return MD5签名
+     */
+    public static String generateMD5Sign(String appId, String req, String key) {
+        try {
+            Map<String, String> params = new HashMap<>();
+            params.put("appId", appId);
+            params.put("req", req);
+            return generateMD5Sign(params, key);
+        } catch (Exception e) {
+            throw new RuntimeException("生成MD5签名失败", e);
+        }
+    }
+    
+    /**
+     * 生成MD5签名
+     * 按照阳光系统的签名规则生成MD5签名
+     * 规则:除cipher/sign节点外,其他非空值按照字典顺序以key1=value1&key2=value2&...拼接生成加密源码A(包含最后一个&),然后追加key得到源码B,最后对源码B进行MD5加密
+     * @param params 请求参数
+     * @param key 密钥
+     * @return MD5签名
+     */
+    public static String generateMD5Sign(Map<String, String> params, String key) {
+        try {
+            Set<String> keySet = params.keySet();
+            TreeSet<String> sortKeySet = new TreeSet<>(keySet);
+            StringBuilder sb = new StringBuilder();
+            for (String k : sortKeySet) {
+                if ("cipher".equals(k) || "sign".equals(k)) {
+                    continue;
+                }
+                String value = params.get(k);
+                if (value != null && !value.trim().isEmpty()) {
+                    sb.append(k).append("=").append(value).append("&");
+                }
+            }
+            sb.append(key);
+            String data = sb.toString();
+            System.out.println("data:"+data);
+            return MD5Util.md5(data);
+        } catch (Exception e) {
+            throw new RuntimeException("生成MD5签名失败", e);
+        }
+    }
+    
+    /**
+     * 将字节数组转换为十六进制字符串
+     * @param bytes 字节数组
+     * @return 十六进制字符串
+     */
+    public static String bytesToHex(byte[] bytes) {
+        StringBuilder hexString = new StringBuilder();
+        for (byte b : bytes) {
+            String hex = Integer.toHexString(b & 0xFF);
+            if (hex.length() == 1) {
+                hexString.append('0');
+            }
+            hexString.append(hex);
+        }
+        return hexString.toString();
+    }
+    
+    /**
+     * 验证签名
+     * 验证阳光系统返回的签名是否正确
+     * @param appId 应用ID
+     * @param req 请求数据
+     * @param sign 签名
+     * @param key 密钥
+     * @return 是否验证通过
+     */
+    public static boolean verifySign(String appId, String req, String sign, String key) {
+        String calculatedSign = generateMD5Sign(appId, req, key);
+        return calculatedSign.equalsIgnoreCase(sign);
+    }
+}

+ 2 - 1
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml

@@ -5,6 +5,7 @@
     <resultMap id="MiddleWareDtoResultMap" type="com.yc.ship.module.trade.controller.admin.order.vo.order.TradeOrderRespVO">
         <id property="id" column="id"/>
         <result property="orderNo" column="order_no"/>
+        <result property="voyageId" column="voyage_id"/>
         <result property="bindId" column="bindId"/>
         <result property="isRead" column="isRead"/>
         <result property="sourceName" column="source_name"/>
@@ -1179,7 +1180,7 @@
 
 
     <select id="getOrderInfo"  resultMap="MiddleWareDtoResultMap">
-        SELECT td.id,td.is_read isRead,td.pay_amount,td.order_no,td.external_order_no,td.visitor_type,td.travel_date,td.sell_method,td.source_id,td.source_name,td.store_id,td.seller_id,td.member_id,td.amount,td.order_status,td.remark,td.share_name,td.create_time,td.is_marketing,td.is_marketing_use,
+        SELECT td.id,td.voyage_id,td.is_read isRead,td.pay_amount,td.order_no,td.external_order_no,td.visitor_type,td.travel_date,td.sell_method,td.source_id,td.source_name,td.store_id,td.seller_id,td.member_id,td.amount,td.order_status,td.remark,td.share_name,td.create_time,td.is_marketing,td.is_marketing_use,
         tou.contact_name contactName,tou.credential_no credentialNo,tou.mobile,tou.county,tou.province,tou.city,td.agency_group_id,
         top.id payId,top.pay_amount payAmount,top.pay_status,top.payment_type,top.payment_date,top.payment_no,
         td.store_name,td.is_invoice, td.confirm_type, td.travel_status, tr.refund_amount,td.finish_status,

+ 3 - 1
ship-server-web/src/main/resources/application-sxtest.yaml

@@ -62,7 +62,7 @@ spring:
   redis:
     host: 10.3.10.50 # 地址
     port: 6379 # 端口
-    database: 3 # 数据库索引
+    database: 11 # 数据库索引
     password: redis123 # 密码,建议生产环境开启
 
 --- #################### 定时任务相关配置 ####################
@@ -236,6 +236,8 @@ yudao:
     out: https://tms2.hbsxly.com/
     in: http://10.3.10.50:80/
   suffixCode: QS # 票号后缀
+  notify: # 第三方通知接口
+    insurance: ${yudao.host.out}/${yudao.web.admin-url}/notify/insurance # 保单通知接口
 justauth:
   enabled: true
   type:

+ 2 - 0
ship-server-web/src/main/resources/application.yaml

@@ -256,6 +256,7 @@ yudao:
   security:
     permit-all_urls:
       - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
+      - /${yudao.web.admin-url}/notify/** # 统一消息服务,接收消息的接口,不需要登录
   websocket:
     enable: true # websocket的开关
     path: /infra/ws # 路径
@@ -292,6 +293,7 @@ yudao:
       - /travl-app-api/trade/payNotify
       - /travl-app-api/trade/refundNotify
       - /travl-app-api/system/sms/callback/aliyun
+      - /${yudao.web.admin-url}/notify/** # 统一消息服务,接收消息的接口,不需要登录
     ignore-visit-urls:
       - /admin-api/system/user/profile/**
     ignore-tables: