外卖平台对接笔记(美团外卖,饿了么外卖)

背景:

针对中台订单中心需要将线上线下门店,订单,商品,以及线上金额对账等进行打通,对饿了么外卖,美团外卖进行了订单对接,当前以落单,重要的订单状态流转,以及对外卖平台直接调用为实现,记录以下实现方案,后期对接外卖平台以该文档持续补充。

一:饿了么外卖对接

    一:饿了么开发平台

1:URL

饿了么商家开放平台

二:应用及配置介绍

            1:个人应用

新建应用,目前新建的是个人应用;如果需要发布正式环境,需要进行上线审核,待审核过后,才可以进行正式环境的使用。

外卖平台对接笔记(美团外卖,饿了么外卖)_第1张图片

2:沙箱环境

进入沙箱环境,会有对应的测试店铺路径和账号密码,然后配置对应的推送URL

该推送URL为饿了么的推送订单以及订单状态变更的URL,需要一个Get请求的路径和Post请求的路径,Get请求路径是饿了么会校验该路径是否合法,必须要使用外网链接,如果没有,可以通过花生壳进行内网穿透

key和Secret是该应用生成token时所需要的固定参数,通过此token可以获取到该应用下所有的门店信息,其绑定是在饿了么商家中心进行绑定(测试门店未发布正式环境无法绑定)

外卖平台对接笔记(美团外卖,饿了么外卖)_第2张图片

3:饿了么商家中心

①:URL:配置正式门店与应用关系

饿了么商家版

外卖平台对接笔记(美团外卖,饿了么外卖)_第3张图片

通过门店下店铺管理页面,对门店进行绑定(注意:门店必须已经正式上线才可以绑定,测试门店无法绑定)

     新增商品时的SKU编码在饿了么落单时会通过订单明细里面的商品对应的扩展字段来下发,可以通过它来定义第三方系统的Sku编码,使饿了么的Sku编码和第三方系统的Sku编码产生映射

外卖平台对接笔记(美团外卖,饿了么外卖)_第4张图片

4:注意

通过对饿了么线上网店信息的修改,也可以将线上网店里面的扩展编码存入第三方系统的线下门店编码,饿了么线上网店的扩展编码在落单时会随着订单落下,通过它可以使饿了么的网店编码和第三方系统的线下门店编码产生映射。

三:落单代码逻辑

1:Get请求接口

(饿了么测试推送URL时使用)

	/**
     * API层:饿了么对接POST接口
     */
    @ApiOperation(value = "饿了么对接GET接口", tags = {"tradeCenterApi"}, nickname = "doGet")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "000000:成功,否则失败")})
    String elementGet();
	
	/**
     * Controller层
     */
 	@Override
    @GetMapping(value = "/element", produces = {"application/json"})
    public String elementGet(){
        log.info("测试是否接收到饿了么消息");
        Map map = new HashMap();
        map.put("message", "OK");
        return JSONObject.toJSONString(map);
    }

2:Post请求接口

(饿了么真正使用的落单URL,主要通过type,消息类型字段判断此次订单落单的场景)

 	/**
     * API层:饿了么对接POST接口
     */
    @ApiOperation(value = "饿了么对接POST接口", tags = {"tradeCenterApi"}, nickname = "doPost")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "000000:成功,否则失败")})
    String elementPost(ElemReqDto elemReqDto);

	/**
     * Controller层
     */
	@Override
    @PostMapping(value = "/element", produces = {"application/json"})
    public String elementPost(@ApiParam(value = "饿了么订单信息", required = true)
                              @Valid @RequestBody ElemReqDto elemReqDto){
        log.info("测试是否接收到饿了么消息:{}",JSONObject.toJSONString(elemReqDto));
        return tradeCenterService.elementPost(elemReqDto);
    }

   /**
     * Service层:饿了么接单以及状态流转
     */
    String elementPost(ElemReqDto elemReqDto);

/**
  * Service实现层:饿了么接单以及状态流转
  */	
@lombok.extern.slf4j.Slf4j
@Service("tradeCenterServiceImpl")
@Transactional(rollbackFor = Exception.class)
public class TradeCenterServiceImpl implements TradeCenterService {
	@Override
    public String elementPost(ElemReqDto elemReqDto) {
        Optional.ofNullable(elemReqDto).orElseThrow(() -> new AppException("饿了么单据不存在"));
        String message = elemReqDto.getMessage();
        log.info("Type类型:{};message消息体:{}", elemReqDto.getType(), message);
        try {
            String type = String.valueOf(elemReqDto.getType());
            if (type.equals("14") || type.equals("17") || type.equals("15")) {
                // TODO: 2021/6/22 取消场景:   
                //14==1:接单前用户取消;2:商户拒绝接单;3:5分支未接单自动取消 
                //** 17==订单取消(门店接单后取消) ** 
                //15==门店接单后用户取消
                type = TradeConstants.ElemEvent.ELEM_CANCEL;
            }
            //去工厂获取其对应的类型
            ElemDomainService orderEvent = ElemFactory.getOrderEvent(type);
            //根据类型找到对应的实现逻辑
            orderEvent.orderEvent(message, type);

            Map map = new HashMap();
            map.put("message", "OK");
            return JSONObject.toJSONString(map);
        } catch (Exception e) {
            return e.getMessage();
        }
    }	
}

3:针对饿了么单据不同场景以及类型采用策略+工厂模式实现

①:工厂类:注入到容器中,通过容器将参数遍历并且存放到map中  

@Service
public class ElemFactory {

    private static Map map = new ConcurrentHashMap();

    public static ElemDomainService getOrderEvent(String type) {
        return map.get(type);
    }

    @Autowired
    public void register(ElemDomainService[] instances) {
        if (instances != null && instances.length > 0) {
            for (ElemDomainService oth : instances) {
                map.put(oth.getEvent(), oth);
            }
        }
    }

}	

②:接口类:定义该策略的接口,一般俩个,一个为获取其对应的Type类型,一个为针对该类型找到对应的实现逻辑

public interface ElemDomainService {

    /**
     * 获取事件类型
     * @return
     */
    String getEvent();

    /**
     * 执行对应逻辑
     */
    void orderEvent(String message,String type);

}

③:接口实现类:以落单实现类为案例

@Service
@Slf4j
public class ElemCreateServiceImpl implements ElemDomainService {
    
	@Autowired
    private WaiMaiReqDtoConvertor waiMaiReqDtoConvertor;

    @Autowired
    TradeCenterDomainService tradeCenterDomainService;
	
    @Override
    public String getEvent() {
        //10代表订单生效,正常需要采用枚举类
        return "10";
    }
    
    @Override
    public void orderEvent(String message, String type) {
        //将消息体的内容转换为对象
        ElemOrderReqDto elemOrderReqDtos = JSONObject.parseObject(message, ElemOrderReqDto.class);
        //将饿了么的消息体对象与第三方系统进行字段转换
        OrderReqDto orderReqDto = waiMaiReqDtoConvertor.ElemDtoToOrderDto(elemOrderReqDtos);
        //进行入库操作
        tradeCenterDomainService.save(orderReqDto);
    }
}

4:以上逻辑所需要的类

①:饿了么信息

/**
 * 饿了么信息
 *
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
public class ElemReqDto implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 应用id,应用创建时系统分配的唯一id varchar
     */
    @ApiModelProperty(value = "应用id,应用创建时系统分配的唯一id")
    @JsonProperty(index = 10)
    private Long appId;

    /**
     * 消息的唯一id,用于唯一标记每个消息 varchar
     */
    @ApiModelProperty(value = "消息的唯一id,用于唯一标记每个消息")
    @JsonProperty(index = 10)
    private String requestId;

    /**
     * 消息类型,参加下方【消息类型】 varchar
     */
    @ApiModelProperty(value = "消息类型,参加下方【消息类型】")
    @JsonProperty(index = 10)
    private Integer type;

    /**
     * JSON格式字符串 varchar
     */
    @ApiModelProperty(value = "JSON格式字符串")
    @JsonProperty(index = 10)
    private String message;

    /**
     * 商户的店铺id varchar
     */
    @ApiModelProperty(value = "商户的店铺id")
    @JsonProperty(index = 10)
    private Long shopId;

    /**
     * 网店对应的门店编码 varchar
     */
    @ApiModelProperty(value = "网店对应的门店编码")
    @JsonProperty(index = 10)
    private String storeCode;

    /**
     * 消息发送的时间戳,每次推送时生成,单位毫秒 varchar
     */
    @ApiModelProperty(value = "消息发送的时间戳,每次推送时生成,单位毫秒")
    @JsonProperty(index = 10)
    private Long timestamp;

    /**
     * 消息的唯一id,用于唯一标记每个消息 varchar
     */
    @ApiModelProperty(value = "消息的唯一id,用于唯一标记每个消息")
    @JsonProperty(index = 10)
    private Long userId;

    /**
     * 消息的唯一id,用于唯一标记每个消息 varchar
     */
    @ApiModelProperty(value = "消息的唯一id,用于唯一标记每个消息")
    @JsonProperty(index = 10)
    private String signature;

}

②:饿了么订单信息

/**
 * 饿了么订单信息
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
public class ElemOrderReqDto implements Serializable{
    private static final long serialVersionUID = 1L;

    /**
     * 外部流水单号 varchar
     */
    @ApiModelProperty(value = "外部流水单号")
    @JsonProperty(index = 10)
    private Long orderId;

    /**
     * 顾客送餐地址 varchar
     */
    @ApiModelProperty(value = "顾客送餐地址")
    @JsonProperty(index = 10)
    private String address;

    /**
     * 下单时间 varchar
     */
    @ApiModelProperty(value = "下单时间")
    @JsonProperty(index = 10)
    private LocalDateTime createdAt;

    /**
     * 用户实际支付配送费  varchar
     */
    @ApiModelProperty(value = "用户实际支付配送费")
    @JsonProperty(index = 10)
    private BigDecimal deliverFee;

    /**
     * 订单备注  varchar
     */
    @ApiModelProperty(value = "订单备注")
    @JsonProperty(index = 10)
    private String description;

    /**
     * 店铺Id varchar
     */
    @ApiModelProperty(value = "店铺Id")
    @JsonProperty(index = 10)
    private Long shopId;

    /**
     * 网店对应的门店编码 varchar
     */
    @ApiModelProperty(value = "网店对应的门店编码")
    @JsonProperty(index = 10)
    private String storeCode;

    /**
     * 订单状态 varchar
     */
    @ApiModelProperty(value = "订单状态")
    @JsonProperty(index = 10)
    private String status;

    /**
     * 订单总价 varchar
     */
    @ApiModelProperty(value = "订单总价")
    @JsonProperty(index = 10)
    private BigDecimal totalPrice;

    /**
     * 订单原价 varchar
     */
    @ApiModelProperty(value = "订单原价")
    @JsonProperty(index = 10)
    private BigDecimal originalPrice;

    /**
     * 订单收货人姓名 varchar
     */
    @ApiModelProperty(value = "订单收货人姓名")
    @JsonProperty(index = 10)
    private String consignee;

    /**
     * 订单业务类型 (0外卖单,1到店自取订单,2企业到店买单) varchar
     */
    @ApiModelProperty(value = "订单业务类型 (0外卖单,1到店自取订单,2企业到店买单)")
    @JsonProperty(index = 10)
    private String orderBusinessType;

    /**
     * 订单明细  varchar
     */
    @ApiModelProperty(value = "订单明细")
    @JsonProperty(index = 10)
    private List groups;

    /**
     * 订单参加活动信息  varchar
     */
    @ApiModelProperty(value = "订单参加活动信息")
    @JsonProperty(index = 10)
    private List orderActivities;

    /**
     * 顾客联系电话  varchar
     */
    @ApiModelProperty(value = "顾客联系电话")
    @JsonProperty(index = 10)
    private List phoneList;

}

③:饿了么订单明细信息

/**
 * 饿了么订单明细信息
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
public class ElemOrderLineReqDto implements Serializable{
    private static final long serialVersionUID = 1L;

    /**
     * 分组名称 varchar
     */
    @ApiModelProperty(value = "分组名称")
    @JsonProperty(index = 10)
    private String name;

    /**
     * 类别:normal:普通商品;discount:赠品 varchar
     */
    @ApiModelProperty(value = "类别:normal:普通商品;discount:赠品")
    @JsonProperty(index = 10)
    private String type;

    /**
     * 商品明细 varchar
     */
    @ApiModelProperty(value = "商品明细")
    @JsonProperty(index = 10)
    private List items;
}

④:饿了么订单明细商品信息

/**
 * 饿了么订单明细商品信息
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
public class ElemItemReqDto implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 商品名称 varchar
     */
    @ApiModelProperty(value = "商品名称")
    @JsonProperty(index = 10)
    private String name;

    /**
     * SkuId varchar
     */
    @ApiModelProperty(value = "SkuId")
    @JsonProperty(index = 10)
    private Long skuId;

    /**
     * 商品分类Id varchar
     */
    @ApiModelProperty(value = "商品分类Id")
    @JsonProperty(index = 10)
    private String categoryId;

    /**
     * 商品单价 varchar
     */
    @ApiModelProperty(value = "商品单价")
    @JsonProperty(index = 10)
    private BigDecimal price;

    /**
     * 总价 varchar
     */
    @ApiModelProperty(value = "总价")
    @JsonProperty(index = 10)
    private BigDecimal total;

    /**
     * 商品数量 varchar
     */
    @ApiModelProperty(value = "商品数量")
    @JsonProperty(index = 10)
    private Integer quantity;

    /**
     * SkuCode
     */
    @ApiModelProperty(value = "SkuCode")
    @JsonProperty(index = 10)
    private String extendCode;

}

⑤:订单转换类(针对自己字段进行修改)

	/**
     * 饿了么订单落单时转换为中心订单(正向)
     *
     * @param elemOrderReqDto
     * @return
     */
    public static OrderReqDto ElemDtoToOrderDto(ElemOrderReqDto elemOrderReqDto) {
        OrderReqDto orderReqDto = OrderReqDto.builder()
                .orderIdOut(String.valueOf(elemOrderReqDto.getOrderId()))
                .saleTime(elemOrderReqDto.getCreatedAt())
                .tradeType(TradeConstants.TradeType.ELEM)
                .orderType(TradeConstants.OrderType.ELEMENT)
                .storeType(TradeConstants.storeType.STORE)
            	//此处的openId代表我们的第三方门店编码,可以直接使用,也可以去数据库查询
                .storeCode(elemOrderReqDto.getOpenId())
                .phoneNumber(String.valueOf(elemOrderReqDto.getPhoneList()))
                .payableAmount(elemOrderReqDto.getTotalPrice())
                .actualAmount(elemOrderReqDto.getTotalPrice())
                .orderAmount(elemOrderReqDto.getOriginalPrice())
                .freightAmount(elemOrderReqDto.getDeliverFee())
                .orderSplit(TradeConstants.orderSplit.NO_SPLIT)
                .orderSupportReverse(TradeConstants.orderSupportReverse.SUPPORT_REVERSE)
                .orderFlatFlag(TradeConstants.FlatFlag.NO_FLAT_FLAG_NOMAL).build();
        //收货地址订单
        OrderAddressReqDto addressReqDto = OrderAddressReqDto.builder()
                .detailAddress(elemOrderReqDto.getAddress())
                .receiver(elemOrderReqDto.getConsignee()).build();
        orderReqDto.setAddress(addressReqDto);

        //扩展信息
        OrderExtReqDto orderExtReqDto = OrderExtReqDto.builder()
                .logisticsStatus(TradeConstants.LogisticsStatus.Logistics)
                .sendTime(LocalDateTime.now()).build();
        orderReqDto.setOrderExt(orderExtReqDto);

        //订货信息
        OrderReservationReqDto orderReservationResDto = OrderReservationReqDto.builder()
				.takeSendTime(LocalDateTime.now())
                .reservationUserName(elemOrderReqDto.getConsignee())
                .phoneNumber(String.valueOf(elemOrderReqDto.getPhoneList()))
                .productionStoreCode(elemOrderReqDto.getOpenId())
                .takeSendStoreCode(elemOrderReqDto.getOpenId())
            	.build();
        orderReqDto.setReservationResDto(orderReservationResDto);

        //订单明细信息
        List elemOrderLineReqDtoList = elemOrderReqDto.getGroups().stream()
                .filter(v -> v.getType().equals("normal"))
                .collect(Collectors.toList());
        List orderLineReqDtoList = new ArrayList<>();
        elemOrderLineReqDtoList.get(0).getItems().forEach(v -> {
            OrderLineReqDto orderLineReqDto = OrderLineReqDto.builder()
                    .actualAmount(v.getTotal())
                    .originPrice(v.getPrice())
                    .actualPrice(v.getPrice())
                	//ExtendCode为我们第三方的商品编码,也可以直接使用美团
                    .itemCode(v.getExtendCode())
                    .skuCode(v.getExtendCode())
                    .itemName(v.getName())
                    .skuQuantity(BigDecimal.valueOf(v.getQuantity()))
                    .storeCode(String.valueOf(elemOrderReqDto.getShopId()))
                    .build();
            orderLineReqDtoList.add(orderLineReqDto);
        });
        orderReqDto.setDetailList(orderLineReqDtoList);
        return orderReqDto;
    }

四:调用饿了么Api(以确认接单,拒绝接单为例)

1:后面采用如上的策略+工厂,不再描述

    /**
     * API层:
     */
    @ApiOperation(value = "确认/拒绝接单", tags = {"tradeCenterApi"})
    @ApiImplicitParams({
            @ApiImplicitParam(name = "orderNo", value = "订单号(逗号拼接,支持多个)", paramType = "query"),
            @ApiImplicitParam(name = "Type", value = "接单:(10:有赞;20:饿了么;30:美团);拒单(-10:有赞;-20:饿了么;-30:美团)", paramType = "query")
    })
    @ApiResponses(value = {@ApiResponse(code = 200, message = "操作是否成功,000000:成功,否则失败", response = MultiOrderResDto.class)})
    ResultDTO conFirmOrder(@ApiIgnore @RequestParam Map params);

  	/**
     * Controller层:
     */
    @Override
    @GetMapping(value = "/conFirmOrder", produces = {"application/json"})
    public ResultDTO conFirmOrder(@ApiIgnore @RequestParam Map params) {
        tradeCenterService.conFirmOrder(params);
        return ResultDTO.ok();
    }

2:确认接单逻辑

/**
 * @Description
 * @Author: dingjunxin
 * @Email: [email protected]
 */
@Service
@Slf4j
public class ElemOrderConfirmServiceImpl implements OrderConfirmDomainService {

    @Autowired
    TradeCenterDomainService tradeCenterDomainService;

    @Override
    public String getEvent() {
        return TradeConstants.OrderConfirmType.ELEMENT;
    }

    @Override
    public void orderEvent(OrderResDto orderResDto, String type) {
        try {
            //此处主要使用饿了么网店编码进行确认接单
            OrderService orderService = WaiMaiUtil.getOrderService(data);
			//此处需要传入饿了么订单号进行确认接单            
            orderService.confirmOrderLite(orderResDto.getOrderIdOut());
        } catch (Exception e) {
            throw new AppException(e.getMessage());
        }
    }
}

3:配置层

    /**
     * 获取OrderService
     */
    public static OrderService getOrderService() {
        //todo 以上正常需要传入网店信息,去找其对应的应用秘钥,当前注释,不再描述
        // 当前应用appKey
        String appKey = "fqRp1D9y1v";
        // 当前应用secret
        String appSecret = "d5c2caf79c4708539f81ff0701160510b3982837";
        // 实例化一个配置类
        Config config = getConfig(false,appKey,appSecret);
        Token token = getToken(config);
        System.out.println(token);
        OrderService orderService = new OrderService(config, token);
        return orderService;
    }

	 /**
     * 获取配置类
     * @param isSandbox 是否沙箱
     * @return
     */
    public static Config getConfig(Boolean isSandbox,String appKey,String appSecret){
        Config config=null;
        if(isSandbox){
            config=new Config(isSandbox, appKey,appSecret);
        }else{
            // TODO 填充正式环境数据
            config=new Config(isSandbox, appKey,appSecret);
        }
        return config;
    }

	  /**
     * 获取对应Token
     */
    public static Token getToken(Config config){
        // 使用config对象,实例化一个授权类
        OAuthClient client = new OAuthClient(config);
        // 使用授权类获取token
        Token token = client.getTokenInClientCredentials();
        return token;
    }

五:饿了么金额计算(结账用,暂无)


二:美团外卖对接

一:美团外卖开发平台

URL

美团开放平台-为美好智慧生活连接更多可能

二:应用及配置介绍

1:应用管理

目前美团可以通过品牌商和服务商的身份入住,当前以品牌商身份进行对接,美团只有一个开发环境,并且当测试门店绑定手机号需要联系美团人员进行绑定,否则找不到对应的测试门店,无法测试下单。

外卖平台对接笔记(美团外卖,饿了么外卖)_第5张图片

   

 2:美团商家中心

①:URL:配置美团的商家绑定对应门店,

当前无法确定门店网店,商品映射关系的参数

        ②:appId和AppSecret用于生成调用美团API的sig

外卖平台对接笔记(美团外卖,饿了么外卖)_第6张图片

③:美团网店与第三方门店绑定

描述:通过该授权页面,可以将美团对应的网店和我们的第三方门店进行绑定,但是有一个问题和饿了么不同,美团推送订单时,所携带的APP_POI_CODE,是可以直接下发我们的门店编码,如果我们没有进行配置,则默认下发美团的网店ID,而如果我们将美团的修改为第三方门店的编码,则直接下发我们的门店编码,此字段只有一个,可以通过页面进行配置,但是由于扩展性,建议落库。

外卖平台对接笔记(美团外卖,饿了么外卖)_第7张图片

外卖平台对接笔记(美团外卖,饿了么外卖)_第8张图片

三:落单代码逻辑

(正向,逆向,状态流转)

1:API层

        

    /**
     * 美团正向单
     */
    @ApiOperation(value = "美团正向单", tags = {"mTPost"}, nickname = "doPost")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "000000:成功,否则失败")})
    Object mTPost(MtReqDto request);

    /**
     * 美团逆向单
     */
    @ApiOperation(value = "美团逆向单", tags = {"mTReversePost"}, nickname = "doPost")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "000000:成功,否则失败")})
    Object mTReversePost(@RequestParam Map params);

    /**
     * 美团订单状态流转POST接口
     */
    @ApiOperation(value = "美团订单状态流转POST接口", tags = {"TriggerMtOrder"}, nickname = "doPost")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "000000:成功,否则失败")})
    Object TriggerMtOrder(@RequestParam Map params);

  2:Controller层

	@Override
    @PostMapping(value = "/mTPost", produces = {"text/html"})
    public Object mTPost(MtReqDto mtReqDto){
        log.info("测试是否接收到美团正向消息:{}",JSONObject.toJSONString(mtReqDto));
        return tradeCenterService.MtTrigger(mtReqDto);
    }

    @Override
    @GetMapping(value = "/mTReversePost", produces = {"text/html"})
    public Object mTReversePost(Map params){
        MtReverseReqDto mtReverseReqDto = MtofBeanUtils.mapToObject(params, MtReverseReqDto.class);
        log.info("测试是否接收到美团逆向消息:{}",JSONObject.toJSONString(mtReverseReqDto));
        return tradeCenterService.MtReverseTrigger(mtReverseReqDto);
    }

    @Override
    @GetMapping(value = "/TriggerMtOrder")
    public Object TriggerMtOrder(Map params){
        log.info("测试是否接收到美团取消消息:{}",JSONObject.toJSONString(params));
        //9:取消(针对某种场景的取消)
        MtReqDto mtReqDto= MtReqDto.builder()
                .order_id(Long.valueOf(params.get("order_id").toString()))
                .status("9")
                .build();
        return tradeCenterService.MtTrigger(mtReqDto);
    }	

3:service层

    /**
     * 美团正向接单以及状态流转
     */
    Object MtTrigger(MtReqDto mtReqDto);

    /**
     * 美团逆向接单以及状态流转
     */
    Object MtReverseTrigger(MtReverseReqDto mtReqDto);

4:Service实现类(采用策略+工厂,如上,不详细描述)

@Override
    public Object MtTrigger(MtReqDto reqDto) {
        Optional.ofNullable(reqDto).orElseThrow(() -> new AppException("美团单据不存在"));

        reqDto.setDetail(URLUtil.decode(reqDto.getDetail()));
        reqDto.setPoi_receive_detail(URLUtil.decode(reqDto.getPoi_receive_detail()));

        String message = URLUtil.decode(JSONObject.toJSONString(reqDto));
        MtReqDto mtReqDto = JSONObject.parseObject(message, MtReqDto.class);

        try {
            
            String type = "MT" + mtReqDto.getStatus();
            
            //策略+工厂
            MtDomainService orderEvent = MtFactory.getOrderEvent(type);
            orderEvent.orderEvent(mtReqDto, type);

            Map map = new HashMap();
            return map.put("data", "OK");

        } catch (Exception e) {
            return e.getMessage();
        }
    }

    @Override
    public Object MtReverseTrigger(MtReverseReqDto reqDto) {
        Optional.ofNullable(reqDto).orElseThrow(() -> new AppException("美团单据不存在"));

        String message = URLUtil.decode(JSONObject.toJSONString(reqDto));
        MtReverseReqDto mtReverseReqDto = JSONObject.parseObject(message, MtReverseReqDto.class);

        try {
            String type = "MT_REVERSE_" + mtReverseReqDto.getRes_type();
            
            //1;3;7;8:取消
            if (mtReverseReqDto.getRes_type() == 1 || mtReverseReqDto.getRes_type() == 3
                    || mtReverseReqDto.getRes_type() == 7 || mtReverseReqDto.getRes_type() == 8) {
                type = "MT_REVERSE_CANCEL";
            } else if (mtReverseReqDto.getRes_type() == 2 || mtReverseReqDto.getRes_type() == 4
                    || mtReverseReqDto.getRes_type() == 5 || mtReverseReqDto.getRes_type() == 6) {
                //2;4;5;6:完成
                type = "MT_REVERSE_FINISH";
            }else if (mtReverseReqDto.getRes_type()==0&& CollectionUtils.isNotEmpty(mtReverseReqDto.getFood())){
                type="MT_REVERSE_PART";
            }
            
            MtDomainService orderEvent = MtFactory.getOrderEvent(type);
            orderEvent.orderReverseEvent(mtReverseReqDto, type);

            Map map = new HashMap();
            return map.put("data", "OK");

        } catch (Exception e) {
            return e.getMessage();
        }
    }

5:使用工具类

①:美团订单表头

/**
 * 美团订单表头
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
@Builder
public class MtReqDto implements Serializable{
    private static final long serialVersionUID = 1L;

    /**
     * 订单ID(数据库中请用bigint(20)存储此字段)
     */
    private Long order_id;
    /**
     * 订单展示ID
     */
    private Long wm_order_id_view;
    /**
     * APP方商家ID
     */
    private String app_poi_code;
    /**
     * 美团商家名称
     */
    private String wm_poi_name;
    /**
     * 美团商家地址
     */
    private String wm_poi_address;
    /**
     * 美团商家电话
     */
    private String wm_poi_phone;
    /**
     * 收件人地址(此字段为用户填写的收货地址,可在开发者中心订阅是否根据经纬度反查地址,若订阅则会在此字段后追加反查结果,并用“@#”符号分隔,如:用户填写地址@#反查结果)
     */
    private String recipient_address;
    /**
     * 收件人电话(请兼容13812345678和13812345678_123456两种号码格式,以便对接隐私号订单,最多不超过20位)
     */
    private String recipient_phone;
    /**
     * 备用隐私号 ["13812345678_1236","13812345678_3456"]
     */
    private List backup_recipient_phone;
    /**
     * 收件人姓名(若用户没有填写姓名,此字段默认为空。可在开发者中心订阅是否用“美团客人”填充此字段)
     */
    private String recipient_name;
    /**
     * 门店配送费
     */
    private BigDecimal shipping_fee;
    /**
     * 总价
     */
    private BigDecimal total;
    /**
     * 原价
     */
    private BigDecimal original_price;
    /**
     * 忌口或备注
     */
    private String caution;
    /**
     * 送餐员电话
     */
    private String shipper_phone;
    /**
     * 订单状态 2:新订单-用户支付完成,待商家接单 4:商家已接单 8:订单已完成 9:订单已取消
     */
    private String status;
    /**
     * 城市ID(目前暂时用不到此信息)
     */
    private Long city_id;
    /**
     * 是否开发票
     */
    private Integer has_invoiced;
    /**
     * 发票抬头
     */
    private String invoice_title;
    /**
     * 纳税人识别号,该信息默认不推送,如有需求可在开发者中心订阅
     */
    private String taxpayer_id;
    /**
     * 创建时间 (注:订单创建时间)
     */
    private Long ctime;
    /**
     * 更新时间
     */
    private Long utime;
    /**
     * 用户预计送达时间,“立即送达”时为0,非0 代表非即时单(包含到店自取,时间为用户到店取餐时间),预订单代表用户下单时填写的预计送达时间,单位是秒,10位时间戳
     */
    private Long delivery_time;
    /**
     * 	是否是第三方配送平台配送,0表否,1表是)
     */
    private Integer is_third_shipping;
    /**
     * 支付类型,1表货到付款,2表在线支付(非支付渠道)
     */
    private Integer pay_type;
    /**
     * 取餐类型(0:普通取餐;1:到店取餐),该信息默认不推送,如有需求可在开发者中心订阅
     */
    private Integer pick_type;
    /**
     * 实际送餐地址纬度
     */
    private Double latitude;
    /**
     * 实际送餐地址经度
     */
    private Double longitude;
    /**
     * 门店当天的推单流水号,该信息默认不推送,如有需求可在开发者中心订阅
     */
    private Integer day_seq;
    /**
     * 用户是否收藏此门店(true, false),该信息默认不推送,如有需求可在开发者中心订阅
     */
    private Boolean is_favorites;
    /**
     * 用户是否第一次在此门店点餐(true, false),该信息默认不推送,如有需求可在开发者中心订阅
     */
    private Boolean is_poi_first_order;
    /**
     * 用餐人数(0:用户没有选择用餐人数;1-10:用户选择的用餐人数;-10:10人以上用餐;88:用户需要餐具;99:用户不需要餐具),该信息默认不推送,如有需求可在开发者中心订阅
     */
    private String dinners_number;
    /**
     * 订单配送方式,该信息默认不推送,如有需求可在开发者中心订阅
     */
    private String logistics_code;
    /**
     * 商家对账信息的json数据,该信息默认不推送,如有需求可在开发者中心订阅
     */
    private String poi_receive_detail;

    /**
     * 第一种:优惠信息,其值为由list序列化得到的json字符串
     */
    private String extras;
    /**
     * 门店平均配送时长,单位为秒
     */
    private String avg_send_time;
    /**
     * 订单来源属性标识,该信息默认不推送,如有需求可在开发者中心订阅
     */
    private String channel;
    /**
     * 开发票方式,0,非自动开票订单,1美团发票合作商家,在线自动开具电子发票
     */
    private Integer invMakeType;
    /**
     * 订单数据状态标记。当订单中部分字段的数据因内部交互异常或网络等原因延迟生成(超时),导致开发者当前获取的订单数据不完整,此时平台对订单数据缺失情况进行标记。如不完整,建议尝试重新查询。注意,平台仅对部分模块的数据完整性进行监察标记(参考incmp_modules字段)。参考值: -1:有数据降级 0:无数据降级
     */
    private Integer incmp_code;
    /**
     *有降级的数据模块的集合,参考值: 0:订单商品详情 1:订单优惠信息 2:商品优惠详情 3:订单用户会员信息 4:订单维度的商家对账信息 5:订单维度的商家对账信息(元) 6:订单收货人地址 7:订单配送方式 8:开放平台用户id 9:部分退款商品信息 10:退货退款物流信息 11:部分订单基本信息(包括订单优惠信息、订单商品详情、门店信息等) 12:sku信息 13:spu信息 14:商品信息(可能是sku或spu等商品相关信息获取时降级) 15:替换折扣价为原价
     */
    private Set incmp_modules;
    /**
     * 其他费用,需要在mcc增加对应需要推送的app_id才推送,List的json string
     */
    private String extendsAmount;

    /**
     * 订单商品详情,其值为由list序列化得到的json字符串
     */
    private String detail;

} 
  

②:美团订单明细信息

/**
 * 美团订单明细信息
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
public class MtOrderLineReqDto implements Serializable{
    private static final long serialVersionUID = 1L;

    /**
     * 分组名称 varchar
     */
    @ApiModelProperty(value = "分组名称")
    @JsonProperty(index = 10)
    private String app_food_code;

    /**
     * sku编码 varchar
     */
    @ApiModelProperty(value = "sku编码")
    @JsonProperty(index = 10)
    private String sku_id;

    /**
     * 商品数量 varchar
     */
    @ApiModelProperty(value = "商品数量")
    @JsonProperty(index = 10)
    private BigDecimal quantity;

    /**
     * 商品单价,不包含餐盒费,此字段默认为活动折扣后价格,可在开发者中心订阅是否替换为原价 varchar
     */
    @ApiModelProperty(value = "商品单价,不包含餐盒费,此字段默认为活动折扣后价格,可在开发者中心订阅是否替换为原价")
    @JsonProperty(index = 10)
    private BigDecimal price;

    /**
     * 餐盒数量,在计算餐盒数量和餐盒费用时,请先按照商品规格维度将餐盒数量向上取整后,再乘以相应的餐盒费单价,计算得出餐盒费用。 varchar
     */
    @ApiModelProperty(value = "餐盒数量,在计算餐盒数量和餐盒费用时,请先按照商品规格维度将餐盒数量向上取整后,再乘以相应的餐盒费单价,计算得出餐盒费用。")
    @JsonProperty(index = 10)
    private String box_num;

    /**
     * 餐盒价格 varchar
     */
    @ApiModelProperty(value = "餐盒价格")
    @JsonProperty(index = 10)
    private BigDecimal box_price;

    /**
     * 单位 varchar
     */
    @ApiModelProperty(value = "单位")
    @JsonProperty(index = 10)
    private String unit;

    /**
     * 商品折扣,默认为1,仅美团商家可设置 varchar
     */
    @ApiModelProperty(value = "商品折扣,默认为1,仅美团商家可设置")
    @JsonProperty(index = 10)
    private String food_discount;

    /**
     * 菜品名称 varchar
     */
    @ApiModelProperty(value = "菜品名称")
    @JsonProperty(index = 10)
    private String food_name;
}

③:美团逆向订单表头

/**
 * 美团逆向订单表头
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
@Builder
public class MtReverseReqDto implements Serializable{
    private static final long serialVersionUID = 1L;

    /**
     * 逆向ID
     */
    private String refund_id;

    /**
     * 订单ID(数据库中请用bigint(20)存储此字段)
     */
    private Long order_id;
    /**
     * 通知类型:apply:发起退款;agree:确认退款;reject:驳回退款;cancelRefund:用户取消退款申请
     */
    private String notify_type;
    /**
     * 退款原因
     */
    private String reason;
    /**
     * 0:未处理;1:商家驳回退款请求;2、商家同意退款;3、客服驳回退款请求;4、客服帮商家同意退款;5、超过3小时自动同意;6、系统自动确认;7:用户取消退款申请;8:用户取消退款申诉
     */
    private Integer res_type;
    /**
     * 是否申诉退款:0-否;1-是
     */
    private Integer is_appeal;
    /**
     * 退款金额
     * 消息类型msg_type:
     * 1:全部退款申请
     * 2:全部退款申请处理(同意or拒绝)
     * 3:全部退款退款成
     * 4:全部退款失败
     * 11:部分退款申请
     * 12:部分退款申请处理(同意or拒绝)
     * 13:部分退款退款成功
     * 14:部分款退款失败
     *  23:重复支付退款)
     * 当 msg_type > 10时,有金额money退款字段
     */
    private String money;

    /**
     * 退款商品信息
     */
    private List food;
}

④:美团逆向订单明细

/**
 * 美团逆向订单明细
 */
@Validated
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel
@Builder
public class ItemLineReqDto implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * APP方菜品id,最大长度128,不同门店可以重复,同一门店内不能重复 varchar
     */
    @ApiModelProperty(value = "APP方菜品id,最大长度128,不同门店可以重复,同一门店内不能重复")
    @JsonProperty(index = 10)
    private String app_food_code;

    /**
     * 退款菜品名称
     */
    @ApiModelProperty(value = "退款菜品名称")
    @JsonProperty(index = 10)
    private String food_name;

    /**
     * sku码
     */
    @ApiModelProperty(value = "sku码")
    @JsonProperty(index = 10)
    private String sku_id;

    /**
     * 单位
     */
    @ApiModelProperty(value = "单位")
    @JsonProperty(index = 10)
    private String spec;

    /**
     * 商品价格
     */
    @ApiModelProperty(value = "商品价格")
    @JsonProperty(index = 10)
    private BigDecimal food_price;

    /**
     * 商品数量
     */
    @ApiModelProperty(value = "商品数量")
    @JsonProperty(index = 10)
    private Integer count;

    /**
     * 打包盒数量
     */
    @ApiModelProperty(value = "打包盒数量")
    @JsonProperty(index = 10)
    private BigDecimal box_num;

    /**
     * 打包盒价格
     */
    @ApiModelProperty(value = "打包盒价格")
    @JsonProperty(index = 10)
    private BigDecimal box_price;

    /**
     * 菜品原价,单位元
     */
    @ApiModelProperty(value = "菜品原价,单位元")
    @JsonProperty(index = 10)
    private BigDecimal origin_food_price;

    /**
     * 退款价格,单位元
     */
    @ApiModelProperty(value = "退款价格,单位元")
    @JsonProperty(index = 10)
    private BigDecimal refund_price;

}

⑤:转换类

    /**
     * 美团订单落单时转换为中心订单(正向)
     *
     * @param
     * @return
     */
    public static OrderReqDto MtDtoToOrderDto(MtReqDto mtReqDto) {
        OrderReqDto orderReqDto = OrderReqDto.builder()
                .orderIdOut(String.valueOf(mtReqDto.getOrder_id()))
                .saleTime(LocalDateTime.now())
                .tradeType(TradeConstants.TradeType.MT)
                .orderType(TradeConstants.OrderType.MT)
                .storeType(TradeConstants.storeType.STORE)
            	//该字段代表的可以是美团自己生成的ID,也可以使用我们第三方的门店编码,通过门店授权进行绑定
                .storeCode(meiTuanReqDto.getApp_poi_code())
                .payableAmount(mtReqDto.getTotal())
                .actualAmount(mtReqDto.getTotal())
                .orderAmount(mtReqDto.getOriginal_price())
                .freightAmount(mtReqDto.getShipping_fee())
                .orderSplit(TradeConstants.orderSplit.NO_SPLIT)
                .orderSupportReverse(TradeConstants.orderSupportReverse.SUPPORT_REVERSE)
                .orderFlatFlag(TradeConstants.FlatFlag.NO_FLAT_FLAG_NOMAL).build();

        //地址信息
        OrderAddressReqDto addressReqDto = OrderAddressReqDto.builder()
                .detailAddress(mtReqDto.getRecipient_address())
                .receiver(mtReqDto.getRecipient_name()).build();
        orderReqDto.setAddress(addressReqDto);

        //扩展信息
        OrderExtReqDto orderExtReqDto = OrderExtReqDto.builder()
                .logisticsStatus(TradeConstants.LogisticsStatus.Logistics).build();
        orderReqDto.setOrderExt(orderExtReqDto);

        //订货信息
        OrderReservationReqDto orderReservationResDto = OrderReservationReqDto.builder()
                .takeSendTime(LocalDateTime.now())
                .reservationUserName(mtReqDto.getRecipient_name())
                .phoneNumber(mtReqDto.getRecipient_phone())
                .productionStoreCode(meiTuanReqDto.getApp_poi_code())
                .takeSendStoreCode(meiTuanReqDto.getApp_poi_code()).build();
        orderReqDto.setReservationResDto(orderReservationResDto);

        //明细信息
        List mtOrderLineReqDtoList = JSONArray.parseArray(mtReqDto.getDetail(), MtOrderLineReqDto.class);
        List orderLineReqDtoList = new ArrayList<>();
        mtOrderLineReqDtoList.forEach(v -> {
            OrderLineReqDto orderLineReqDto = OrderLineReqDto.builder()
                    .actualAmount(v.getPrice())
                    .originPrice(v.getPrice())
                    .actualPrice(v.getPrice())
                	//该字段为我们自定义的商品编码,也可使用美团自己生成的,但字段名非这个
                    .itemCode(v.getSku_id())
                    .skuCode(v.getSku_id())
                    .itemName(v.getFood_name())
                    .skuQuantity(v.getQuantity())
                    .storeCode(meiTuanReqDto.getApp_poi_code())
                    .build();
            orderLineReqDtoList.add(orderLineReqDto);
        });
        orderReqDto.setDetailList(orderLineReqDtoList);
        return orderReqDto;
    }

    /**
     * 美团订单落单时转换为中心订单(逆向全额)
     */
    public static ReverseOrderReqDto MtReverseToOrderDto(MtReverseReqDto mtReverseReqDto, OrderResDto orderResDto) {
        BigDecimal money = BigDecimal.ZERO;
        if (mtReverseReqDto.getMoney() != null) {
            money = new BigDecimal(mtReverseReqDto.getMoney());
        }
        ReverseOrderReqDto reverseOrderDto = ReverseOrderReqDto.builder()
                .orderIdOut(mtReverseReqDto.getRefund_id())
                .orderNo(orderResDto.getOrderNo())
                .tradeType(TradeConstants.TradeType.MT_REVERSE)
                .saleTime(LocalDateTime.now())
                .storeType(orderResDto.getStoreType())
                .storeCode(orderResDto.getStoreCode())
                .relateOrderType(TradeConstants.relateOrderType.GENERAL_RELATE_ORDER)
                .actualAmount(mtReverseReqDto.getMoney() != null ? money : null)
                .parentOrderNo(orderResDto.getOrderNo())
                .relateOriginNo(orderResDto.getOrderNo())
                .reverseType(TradeConstants.relateOrderType.GENERAL_RELATE_ORDER)
                .remark(mtReverseReqDto.getReason()).build();
        List orderLineReqDtoList = new ArrayList<>();

        orderResDto.getDetailList().forEach(v -> {
            ReverseOrderLineReqDto reverseOrderLineReqDto = ReverseOrderLineReqDto.builder()
                    .orderNo(orderResDto.getOrderNo())
                    .storeCode(orderResDto.getStoreCode())
                    .skuCode(v.getSkuCode())
                    .itemCode(v.getItemCode())
                    .itemName(v.getItemName())
                    .payableAmount(v.getActualAmount())
                    .actualAmount(v.getActualAmount())
                    .skuQuantity(v.getSkuQuantity())
                    .refundType(TradeConstants.relateOrderType.GENERAL_RELATE_ORDER).build();
            orderLineReqDtoList.add(reverseOrderLineReqDto);
        });
        reverseOrderDto.setDetailList(orderLineReqDtoList);
        return reverseOrderDto;
    }

四:调用美团API接口(以确认接单为案例)

前面如上,采用策略+工厂模式进行扩展,不再描述

①:Service实现类

@Service
@Slf4j
public class MtOrderConfirmServiceImpl implements OrderConfirmDomainService {

    @Autowired
    private WaiMaiReqDtoConvertor waiMaiReqDtoConvertor;

    @Autowired
    TradeCenterDomainService tradeCenterDomainService;

    @Autowired
    private RestTemplateUtils restTemplateUtils;

    @Override
    public String getEvent() {
        return TradeConstants.OrderConfirmType.MT;
    }

    @Override
    public void orderEvent(OrderResDto orderResDto, String type) {
        try {
            //获取Sig
            long timestamp = WaiMaiUtil.getMtTimestamp();
            String md5Input = DAConfigConst.Url.MT_CONFIRM + "?app_id=" + WaiMaiUtil.MtConfig.appId 
                	+ "&order_id=" + orderResDto.getOrderIdOut()
                	+ "×tamp=" + timestamp + WaiMaiUtil.MtConfig.appSecret;
			
         	//此Sig为调用美团Api接口必传参数,由美团URL加API上列举系统参数进行MD5加密生成
            //可通过API控制台先行测试
            String sig = WaiMaiUtil.getSig(md5Input);
            //调用美团参数
            Map params = new MapUtils()
                    .put("app_id", WaiMaiUtil.MtConfig.appId)
                    .put("order_id", orderResDto.getOrderIdOut())
                    .put("timestamp", timestamp)
                    .put("sig", sig);
            log.info("确认接单参数:{}", JSONObject.toJSONString(params));
            //此处采用工具类直接调用外部接口,DA为URL路径
            String result = HttpUtil.sendGet(DAConfigConst.Url.MT_CONFIRM, params);
			
            //此处针对返回报错信息抛出对应异常
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (jsonObject.get("error")!=null){
                MtErrorLineVo error = JSONObject.parseObject(jsonObject.get("error").toString(), MtErrorLineVo.class);
                throw new AppException(error.getMsg());
            }
        } catch (Exception e) {
            throw new AppException(e.getMessage());
        }
    }
}

②:配置类

    public static class MtConfig{
        public static final String appId="4746";

        public static final String appSecret="ec3498174474570a57bcb540dbd537cb";
    }

    /**
     * 获取时间戳
     * @return
     */
    public static long getMtTimestamp(){
        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        return ldt.toEpochSecond(ZoneOffset.ofHours(8));
    }

    /**
     * MD5加密Sig
     * @param sig
     * @return
     */
    public static String getSig(String sig){
       return Md5Util.encryptMessage(sig);
    }

③:MD5加密类

/**
 * @Description Md5加密工具类
 */
public class Md5Util {

    /**
     * 加密明文
*

* 加密步骤:
* 1.先进行MD5加密
* 4.加密后的明文转换为16进制
* * @param message 明文 * @return */ public static String encryptMessage(String message) { /** * 进行Md5加密 */ byte[] d5Sha1Data = DigestUtils.md5(EncodingUtils.getBytes( (message == null ? "" : message), "utf-8")); /** * 加密后的信息转换为16进制 */ StringBuilder builder = new StringBuilder(); for (byte b : d5Sha1Data) { builder.append(String.format("%02x", new Integer(b & 0xff))); } return builder.toString(); }

 
  

五:美团金额对账

你可能感兴趣的:(美团外卖,饿了么外卖,外卖对接)