package com.iformall.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.iformall.common.ErrorCode;
import com.iformall.common.IdWorker;
import com.iformall.domain.po.*;
import com.iformall.domain.vo.PromotionCalc;
import com.iformall.domain.vo.WxCouponOrderCVo;
import com.iformall.domain.vo.WxLevelMerchantCVo;
import com.iformall.enums.*;
import com.iformall.exception.MallinkException;
import com.iformall.mapper.*;
import com.iformall.pay.WxPayConstant;
import com.iformall.service.*;
import io.swagger.models.auth.In;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
@AllArgsConstructor
public class PosServiceImpl implements PosService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 东软时间格式, 4位年2位月2位日2位24小时制时2位分2位秒3位毫秒    时区+8 北京时间
    private final SimpleDateFormat neuDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    private final SimpleDateFormat mydateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private final WxMallService mallService;
    private final PosMallConfigService posMallConfigService;
    private final WxCUserService cUserService;
    private final WxLevelConfigService levelConfigService;
    private final WxCouponOrderService couponOrderService;
    private final WxMerchantService merchantService;
    private final WxMerchantBUserService merchantBUserService;
    private final PosCouponOrderVerifyService posCouponOrderVerifyService;
    private final WxScoreRulesService scoreRulesService;
    private final WxCreditHistoryService creditHistoryService;
    private final WxCardInfoService cardInfoService;
    private final WxCardSpendService cardSpendService;

    private final WxOrderMapper orderMapper;
    private final WxPayOrderMapper payOrderMapper;
    private final WxRefundOrderMapper refundOrderMapper;
    private final WxCouponOrderMapper couponOrderMapper;
    private final WxCouponMerchantMapper couponMerchantMapper;
    private final WxCouponMapper couponMapper;


    /**
     * 门店用户登录检查
     * @param params
     * @return
     */
    @Override
    public JSONObject checkUserPassword(Map<String, String> params) throws MallinkException {
        JSONObject retMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID); 		     // 租户ID
        String phone    = params.get(WxPayConstant.PHONE); 		         // buUser's Phone
        String password = params.get(WxPayConstant.PASSWORD); 		     // buUser's password

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        }
        if (StringUtils.isBlank(phone)) {
            errParam(WxPayConstant.PHONE);
        }
        if (StringUtils.isBlank(password)) {
            errParam(WxPayConstant.PASSWORD);
        }
        WxMall mall = mallService.getByTenantId(tenantId);
        if (mall == null) {
            logger.error(ErrorCode.MALL_INFO_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.MALL_INFO_NOT_FOUND);
        }

        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.MALL_INFO_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.MALL_INFO_NOT_FOUND);
        }

        WxMerchantBUser user = new WxMerchantBUser();
        user.setTenantId(tenantId);
        user.setPhone(phone);
        user.setStatus(EnumMerchantBUserStatus.VALID.getCode());
        WxMerchantBUser user1 = null;

        try {
            user1 = merchantBUserService.getBUserByAppId(user);
        } catch (Exception e) {
            String errMessage = ErrorCode.MERCHANT_BUSER_NOT_VALID.getMessage() + "phone: " + phone + ", " +e.getMessage();
            throw new MallinkException(ErrorCode.MERCHANT_BUSER_NOT_VALID.getCode(), errMessage);
        }
        if (user1 != null) {
            // check merchant 状态
            WxMerchant merchant = checkAndGetMerchant(user1.getMerchantId());
            // check password
            if (user1.getUserPwd().equalsIgnoreCase(password)) {
                retMap.put(WxPayConstant.TENANT_ID, user1.getTenantId());
                retMap.put(WxPayConstant.MALL_NAME, mall.getName());
                retMap.put(WxPayConstant.MERCHANT_ID, String.valueOf(user1.getMerchantId()));
                retMap.put(WxPayConstant.MERCHANT_NAME, merchant.getName());
                retMap.put(WxPayConstant.MERCHANT_IMG, merchant.getImgUrl());
                retMap.put(WxPayConstant.BUSER_ID, String.valueOf(user1.getId()));

                JSONObject qrCodeInfo = new JSONObject();
                qrCodeInfo.put(WxPayConstant.REG_QRCODE_URL, mall.getImgQrcodeWeapp());
                qrCodeInfo.put(WxPayConstant.POS_QRCODE_RULE, mall.getPosQrcodeRule());
                retMap.put(WxPayConstant.QRCODE, qrCodeInfo);

                JSONObject memInfo = new JSONObject();
                memInfo.put(WxPayConstant.DISCOUNT, String.valueOf(config.getDiscount().equals(EnumPosEnableType.Enable.getCode())));
                memInfo.put(WxPayConstant.COUPON, String.valueOf(config.getCoupon()));
                memInfo.put(WxPayConstant.COUPON_DES, EnumPosCouponEnableType.getEnum(config.getCoupon()).getMessage());
                memInfo.put(WxPayConstant.CARD, String.valueOf(config.getCard().equals(EnumPosEnableType.Enable.getCode())));
                retMap.put(WxPayConstant.MEM_CONFIG, memInfo);

                return retMap;
            } else {
                logger.error(ErrorCode.PASSWORD_ERROR.getMessage());
                throw new MallinkException(ErrorCode.PASSWORD_ERROR);
            }
        } else {
            String errMessage = "B端用户不存在, phone: " + phone;
            throw new MallinkException(ErrorCode.USER_IS_EMPTY.getCode(), errMessage);
        }
    }

    @Override
    public JSONObject queryAndCalcMem(Map<String, String> params) throws MallinkException {
        JSONObject dataMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID);              // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID);       // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	        // POS操作员ID
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE);         // 交易场景
        String orderCreateSN = params.get(WxPayConstant.ORDER_CREATE_SN);   // POS SN号
        String coListStr = params.get(WxPayConstant.COUPON_ORDER_LIST); 	// 优惠券列表

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        } else {
            dataMap.put(WxPayConstant.TENANT_ID, tenantId);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        } else {
            dataMap.put(WxPayConstant.MERCHANT_ID, merchantIdStr);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        } else {
            dataMap.put(WxPayConstant.BUSER_ID, buUserIdStr);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        } else {
            dataMap.put(WxPayConstant.SCENE_TYPE, sceneTypeStr);
        }
        if (StringUtils.isBlank(orderCreateSN)) {
            errParam(WxPayConstant.ORDER_CREATE_SN);
        } else {
            dataMap.put(WxPayConstant.ORDER_CREATE_SN, orderCreateSN);
        }
        if (StringUtils.isBlank(coListStr)) {
            errParam(WxPayConstant.COUPON_ORDER_LIST);
        }

        Integer iSceneType;
        try {
            iSceneType = Integer.valueOf(sceneTypeStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        // 1. POS 支持 配置
        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.POS_CONFIG_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_NOT_FOUND);
        }

        // 2. 商户ID, B端用户ID
        Long merchantId, buUserId;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        // 3. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 4. check buUser
        WxMerchantBUser buUser = checkAndGetMerchantUser(buUserId, merchantId);

        EnumPosSceneType sceneType = EnumPosSceneType.getEnum(iSceneType);
        JSONObject promotionData = new JSONObject();
        JSONObject promotionInfo = new JSONObject();
        JSONObject promotionPayment = new JSONObject();
        PromotionCalc promotionCalc = new PromotionCalc();
        if (sceneType.equals(EnumPosSceneType.VERIFY)) {
            if (config.getCoupon().equals(EnumPosCouponEnableType.Disable.getCode())) {
                logger.error(ErrorCode.POS_CONFIG_MEM_COUPON_DISABLE.getMessage());
                throw new MallinkException(ErrorCode.POS_CONFIG_MEM_COUPON_DISABLE);
            }
            JSONObject promotionCoupon = new JSONObject();
            JSONArray couponList = new JSONArray();

            JSONArray couponOrderArr = JSON.parseArray(coListStr);
            if (couponOrderArr.size() != 1) {
                String errMessage = "核销只能使用一张券";
                logger.error(errMessage);
                throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), errMessage);
            }
            String couponOrderIdStr = couponOrderArr.getString(0);
            if (couponOrderIdStr != null) {
                try {
                    JSONObject couponPromoMap = calcOneCouponVerify(promotionCalc, couponOrderIdStr, merchantId);
                    couponList.add(couponPromoMap);
                } catch (MallinkException e) {
                    throw new MallinkException(e.getErrorCode(), e.getMessage());
                } catch (Exception e) {
                    throw new MallinkException(500, e.getMessage());
                }
            }
            promotionCoupon.put(WxPayConstant.COUPON_ORDER_LIST, couponList);
            // 券优惠总信息
            promotionCoupon.put(WxPayConstant.COUPON_PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
            promotionCoupon.put(WxPayConstant.REMAIN_AMOUNT, promotionCalc.getAmountLeftAfterPay());
            promotionInfo.put(WxPayConstant.COUPON, promotionCoupon);
        } else if(sceneType.equals(EnumPosSceneType.PAY)) {
            JSONObject promotionCoupon = new JSONObject();
            JSONArray couponList = new JSONArray();

            String memIdStr = params.get(WxPayConstant.MEM_ID);                 // 会员ID
            String memPhoneStr = params.get(WxPayConstant.MEM_PHONE);           // 会员手机
            String cardIdStr = params.get(WxPayConstant.CARD_ID);               // 卡ID
            String orderAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);     // POS订单金额(单位:分)
            String orderAmountLeftStr = params.get(WxPayConstant.ORDER_AMOUNT_LEFT);  // POS订单金额(单位:分)
            if (StringUtils.isBlank(orderAmountStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT);
            } else {
                dataMap.put(WxPayConstant.ORDER_AMOUNT, orderAmountStr);
            }
            if (StringUtils.isBlank(orderAmountLeftStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT_LEFT);
            } else {
                dataMap.put(WxPayConstant.ORDER_AMOUNT_LEFT, orderAmountLeftStr);
            }

            WxCUser user = null;
            if (StringUtils.isNotBlank(cardIdStr)) {
                Long cardId;
                try {
                    cardId = Long.valueOf(cardIdStr);
                } catch (NumberFormatException e) {
                    logger.error(e.getMessage());
                    throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
                }
                WxCouponOrder couponOrder = couponOrderMapper.selectById(cardId);
                if (couponOrder == null) {
                    throw new MallinkException(ErrorCode.CARD_IS_NOT_FOUND);
                }
                user = cUserService.getById(couponOrder.getOwnerId());
            } else {
                if (StringUtils.isBlank(memIdStr) && StringUtils.isBlank(memPhoneStr)) {
                    errParam2(WxPayConstant.MEM_ID, WxPayConstant.MEM_PHONE);
                }
                // 10. 获取会员信息
                user = getMemUser(tenantId, memIdStr, memPhoneStr);
            }

            if (user == null) {
                logger.error(ErrorCode.USER_NOT_MEMBER.getMessage());
                throw new MallinkException(ErrorCode.USER_NOT_MEMBER);
            }
            if (user != null) {
                dataMap.put(WxPayConstant.MEM_ID, user.getId().toString());
                dataMap.put(WxPayConstant.MEM_PHONE, user.getPhone());
            }
            // 11. 订单金额
            Integer orderAmount, orderAmountLeft;
            try {
                orderAmount = Integer.valueOf(orderAmountStr);
                orderAmountLeft = Integer.valueOf(orderAmountLeftStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
            }
            promotionCalc.setOrderAmount(orderAmount);
            promotionCalc.setOrderAmountLeft(orderAmountLeft);
            promotionCalc.setPromotionAmount(0);
            promotionCalc.setAmountLeftAfterPay(orderAmountLeft);
            // 12. 会员折扣
            JSONObject discountObj = getMemLevelDiscount(promotionCalc, config.getDiscount(), tenantId, user, merchantId);
            promotionInfo.put(WxPayConstant.MEMBER_DISCOUNT, discountObj);
            // 14. 获取用户可用券列表
            if (!config.getCoupon().equals(EnumPosCouponEnableType.Disable.getCode())) {
                Map<String, Object> coQ = new HashMap<>();
                coQ.put("tenantId", tenantId);
                coQ.put("cUserId", user.getId());
                coQ.put("merchantId", merchantId);
                List<WxCouponOrderCVo> avaCoList = couponOrderMapper.findAvailCouponOrder(coQ);

                JSONArray selCoArr = JSONObject.parseArray(coListStr);
                if (config.getCoupon().equals(EnumPosCouponEnableType.SingleEnable.getCode())) {
                    if (selCoArr.size() > 1) { // 为空时，返回所有可用券列表
                        String errMessage = "支付只能使用一张券";
                        logger.error(errMessage);
                        throw new MallinkException(ErrorCode.POS_COUPON_PAY_ERROR.getCode(), errMessage);
                    }
                    for (int i = 0; i < avaCoList.size(); i++) {
                        WxCouponOrderCVo couponOrderCVo = avaCoList.get(i);
                        boolean selCoupon = isSelCoupon(selCoArr, couponOrderCVo.getId());
                        try {
                            JSONObject couponObj = calcOneCouponPay(promotionCalc, couponOrderCVo, selCoupon);
                            couponList.add(couponObj);
                        } catch (MallinkException e) {
                            if (selCoupon) {
                                throw new MallinkException(e.getErrorCode(), e.getMessage());
                            }
                        } catch (Exception e) {
                            if (selCoupon) {
                                throw new MallinkException(500, e.getMessage());
                            }
                        }
                    }
                } else if (config.getCoupon().equals(EnumPosCouponEnableType.MultiEnable.getCode())) {
                    for (int i = 0; i < avaCoList.size(); i++) {
                        WxCouponOrderCVo couponOrderCVo = avaCoList.get(i);
                        boolean selCoupon = isSelCoupon(selCoArr, couponOrderCVo.getId());
                        try {
                            JSONObject couponObj = calcOneCouponPay(promotionCalc, couponOrderCVo, selCoupon);
                            couponList.add(couponObj);
                        } catch (MallinkException e) {
                            throw new MallinkException(e.getErrorCode(), e.getMessage());
                        } catch (Exception e) {
                            throw new MallinkException(500, e.getMessage());
                        }
                    }
                }
            }
            promotionCoupon.put(WxPayConstant.COUPON_ORDER_LIST, couponList);
            // 券优惠总信息
            promotionCoupon.put(WxPayConstant.COUPON_PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
            promotionCoupon.put(WxPayConstant.REMAIN_AMOUNT, promotionCalc.getAmountLeftAfterPay());
            promotionInfo.put(WxPayConstant.COUPON, promotionCoupon);
        }
        // 构造支付信息
        promotionPayment.put(WxPayConstant.ORDER_AMOUNT, promotionCalc.getOrderAmount());
        promotionPayment.put(WxPayConstant.ORDER_AMOUNT_LEFT, promotionCalc.getOrderAmountLeft());
        promotionPayment.put(WxPayConstant.PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
        promotionPayment.put(WxPayConstant.AMOUNT_LEFT_AFTER_PAY, promotionCalc.getAmountLeftAfterPay());
        promotionData.put(WxPayConstant.PAYMENT_PREDICTION, promotionPayment);
        promotionData.put(WxPayConstant.PROMOTION_INFO, promotionInfo);
        dataMap.put(WxPayConstant.PROMOTION_DATA, JSON.toJSONString(promotionData));
        return dataMap;
    }

    /**
     * 单券核销计算
     * @param promotionCalc
     * @param merchantId
     * @param couponOrderIdStr
     * @return
     */
    private JSONObject calcOneCouponVerify(PromotionCalc promotionCalc, String couponOrderIdStr, Long merchantId) {
        JSONObject retObj = new JSONObject();
        Date now = new Date();
        // 1. 获取券包信息
        WxCouponOrderCVo couponOrderCVo = checkAndGetCouponOrder(couponOrderIdStr, merchantId, now);

        // 2. 检查券类型 核销可用(4, 6, 8, 9)
        EnumCouponType couponType = EnumCouponType.getEnum(couponOrderCVo.getCouponType());
        if (!(couponType.getCode().equals(EnumCouponType.COUPON_MULTIMCH.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_LIPIN.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_PRESS.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_GROUP.getCode()))
        ) {
            logger.error(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT.getMessage());
            throw new MallinkException(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT);
        }
        retObj.put(WxPayConstant.COUPON_ID, couponOrderIdStr);
        retObj.put(WxPayConstant.IS_SELECTED, EnumPosSelect.SELECTED.getCode());
        retObj.put(WxPayConstant.TITLE, couponOrderCVo.getTitle());
        retObj.put(WxPayConstant.COUPON_TYPE, couponType.getMessage());
        retObj.put(WxPayConstant.COVER_IMG, couponOrderCVo.getCoverImg());
        retObj.put(WxPayConstant.PRICE, couponOrderCVo.getPrice());
        retObj.put(WxPayConstant.SALE_PRICE, couponOrderCVo.getSalePrice());
        retObj.put(WxPayConstant.COUPON_DES, String.format("过期时间: %s", mydateFormat.format(couponOrderCVo.getExpiredTime())));
        promotionCalc.setOrderAmount(couponOrderCVo.getPrice());
        promotionCalc.setOrderAmountLeft(couponOrderCVo.getPrice());
        promotionCalc.setPromotionAmount(couponOrderCVo.getPrice());
        promotionCalc.setAmountLeftAfterPay(0);
        return retObj;
    }

    /**
     * 单券核销
     * @param promotionCalc
     * @param merchantId
     * @param couponOrderIdStr
     * @return
     */
    public JSONObject oneCouponVerify(PromotionCalc promotionCalc, String couponOrderIdStr, Long merchantId, Long buUserId, Long posOrderId) {
        JSONObject retObj = new JSONObject();
        Date now = new Date();
        // 1. 获取券包信息
        WxCouponOrderCVo couponOrderCVo = checkAndGetCouponOrder(couponOrderIdStr, merchantId, now);

        // 2. 检查券类型 核销可用(4, 6, 8, 9)
        EnumCouponType couponType = EnumCouponType.getEnum(couponOrderCVo.getCouponType());
        if (!(couponType.getCode().equals(EnumCouponType.COUPON_MULTIMCH.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_LIPIN.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_PRESS.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_GROUP.getCode()))
        ) {
            logger.error(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT.getMessage());
            throw new MallinkException(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT);
        }
        // 5. get coupon
        WxCoupon wxCoupon = couponMapper.selectById(couponOrderCVo.getCouponId());
        if (wxCoupon == null) {
            logger.error("券: coupon-" + wxCoupon.getId() + "不存在" + "couponOrder-" + couponOrderCVo.getId());
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_NULL);
        }

        try {
            doVerify(retObj, EnumPosActionType.VERIFY_INDEPENT, couponOrderCVo, wxCoupon, merchantId, buUserId, posOrderId);
        } catch (MallinkException e) {
            logger.error(e.getMessage());
            throw new MallinkException(e.getErrorCode(), e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new MallinkException(500, e.getMessage());
        }
        retObj.put(WxPayConstant.COUPON_ID, couponOrderIdStr);
        retObj.put(WxPayConstant.IS_SELECTED, EnumPosSelect.SELECTED.getCode());
        retObj.put(WxPayConstant.TITLE, couponOrderCVo.getTitle());
        retObj.put(WxPayConstant.COUPON_TYPE, couponType.getMessage());
        retObj.put(WxPayConstant.COVER_IMG, couponOrderCVo.getCoverImg());
        retObj.put(WxPayConstant.PRICE, couponOrderCVo.getPrice());
        retObj.put(WxPayConstant.SALE_PRICE, couponOrderCVo.getSalePrice());
        retObj.put(WxPayConstant.COUPON_DES, String.format("过期时间: %s", mydateFormat.format(couponOrderCVo.getExpiredTime())));
        promotionCalc.setOrderAmount(couponOrderCVo.getPrice());
        promotionCalc.setOrderAmountLeft(couponOrderCVo.getPrice());
        promotionCalc.setPromotionAmount(couponOrderCVo.getPrice());
        promotionCalc.setAmountLeftAfterPay(0);
        return retObj;
    }

    /**
     * 券支付计算
     * @param promotionCalc
     * @param couponOrderCVo
     * @param couponSelected
     * @return
     */
    private JSONObject calcOneCouponPay(PromotionCalc promotionCalc, WxCouponOrderCVo couponOrderCVo, boolean couponSelected) {
        JSONObject retObj = new JSONObject();
        Date now = new Date();

        // 2. 检查券类型 支付可用(1, 2, 6),
        EnumCouponType couponType = EnumCouponType.getEnum(couponOrderCVo.getCouponType());
        if (!(couponType.getCode().equals(EnumCouponType.COUPON_MULTIMCH.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_MANJIAN.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_DAIJIN.getCode()))) {
            logger.error(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT.getMessage());
            throw new MallinkException(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT);
        }

        // 3. 满减券检查使用金额
        if (couponType.equals(EnumCouponType.COUPON_MANJIAN)) {
            if (promotionCalc.getOrderAmount() < couponOrderCVo.getUsePrice()) {
                logger.error(ErrorCode.VERIFY_MANJIAN_USER_PRICE_NOT_TOUCH.getMessage());
                throw new MallinkException(ErrorCode.VERIFY_MANJIAN_USER_PRICE_NOT_TOUCH);
            }
        }

        // 4. 生成优惠券抵扣信息
        retObj.put(WxPayConstant.COUPON_ID, couponOrderCVo.getId().toString());
        retObj.put(WxPayConstant.TITLE, couponOrderCVo.getTitle());
        retObj.put(WxPayConstant.COUPON_TYPE, couponType.getMessage());
        retObj.put(WxPayConstant.COVER_IMG, couponOrderCVo.getCoverImg());
        retObj.put(WxPayConstant.PRICE, couponOrderCVo.getPrice());
        retObj.put(WxPayConstant.SALE_PRICE, couponOrderCVo.getSalePrice());
        if (couponType.equals(EnumCouponType.COUPON_MANJIAN)) {
            retObj.put(WxPayConstant.USE_PRICE, couponOrderCVo.getUsePrice());
            String des = String.format("%s-满%s元可用,可抵%s元, 过期时间: %s",
                    couponType.getMessage(), couponOrderCVo.getUsePriceStr(), couponOrderCVo.getPriceStr(),
                    mydateFormat.format(couponOrderCVo.getExpiredTime()));
            retObj.put(WxPayConstant.COUPON_DES, des);
        } else {
            String des = String.format("%s-可抵%s元, 过期时间: %s",
                    couponType.getMessage(), couponOrderCVo.getPriceStr(),
                    mydateFormat.format(couponOrderCVo.getExpiredTime()));
            retObj.put(WxPayConstant.COUPON_DES, des);
        }
        if (couponSelected) {
            retObj.put(WxPayConstant.IS_SELECTED, String.valueOf(EnumPosSelect.SELECTED.getCode()));
            Integer remainAmount = promotionCalc.getAmountLeftAfterPay() - couponOrderCVo.getPrice();
            if (remainAmount < 0) {
                remainAmount = 0;
            }
            Integer discountAmount = promotionCalc.getAmountLeftAfterPay() - remainAmount;
            promotionCalc.setPromotionAmount(discountAmount);
            promotionCalc.setAmountLeftAfterPay(remainAmount);
        }
        return retObj;
    }

    /**
     * 券支付计算
     * @param promotionCalc
     * @param couponOrderCVo
     * @param couponSelected
     * @return
     */
    private JSONObject oneCouponPay(PromotionCalc promotionCalc, WxCouponOrderCVo couponOrderCVo, Long merchantId, Long buUserId, Long posOrderId, boolean couponSelected) {
        JSONObject retObj = new JSONObject();
        Date now = new Date();

        // 2. 检查券类型 支付可用(1, 2, 6),
        EnumCouponType couponType = EnumCouponType.getEnum(couponOrderCVo.getCouponType());
        if (!(couponType.getCode().equals(EnumCouponType.COUPON_MULTIMCH.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_MANJIAN.getCode()) ||
                couponType.getCode().equals(EnumCouponType.COUPON_DAIJIN.getCode()))) {
            logger.error(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT.getMessage());
            throw new MallinkException(ErrorCode.VERIFY_COUPON_TYPE_NOT_SUPPORT);
        }

        // 3. 满减券检查使用金额
        if (couponType.equals(EnumCouponType.COUPON_MANJIAN) && couponSelected) {
            if (promotionCalc.getOrderAmount() < couponOrderCVo.getUsePrice()) {
                logger.error(ErrorCode.VERIFY_MANJIAN_USER_PRICE_NOT_TOUCH.getMessage());
                throw new MallinkException(ErrorCode.VERIFY_MANJIAN_USER_PRICE_NOT_TOUCH);
            }
        }

        // 4. 生成优惠券抵扣信息
        retObj.put(WxPayConstant.COUPON_ID, couponOrderCVo.getId().toString());
        retObj.put(WxPayConstant.TITLE, couponOrderCVo.getTitle());
        retObj.put(WxPayConstant.COUPON_TYPE, couponType.getMessage());
        retObj.put(WxPayConstant.COVER_IMG, couponOrderCVo.getCoverImg());
        retObj.put(WxPayConstant.PRICE, couponOrderCVo.getPrice());
        retObj.put(WxPayConstant.SALE_PRICE, couponOrderCVo.getSalePrice());
        if (couponType.equals(EnumCouponType.COUPON_MANJIAN)) {
            retObj.put(WxPayConstant.USE_PRICE, couponOrderCVo.getUsePrice());
            String des = String.format("%s-满%s元可用,可抵%s元, 过期时间: %s",
                    couponType.getMessage(), couponOrderCVo.getUsePriceStr(), couponOrderCVo.getPriceStr(),
                    mydateFormat.format(couponOrderCVo.getExpiredTime()));
            retObj.put(WxPayConstant.COUPON_DES, des);
        } else {
            String des = String.format("%s-可抵%s元, 过期时间: %s",
                    couponType.getMessage(), couponOrderCVo.getPriceStr(),
                    mydateFormat.format(couponOrderCVo.getExpiredTime()));
            retObj.put(WxPayConstant.COUPON_DES, des);
        }
        if (couponSelected) {
            // 5. get coupon
            WxCoupon wxCoupon = couponMapper.selectById(couponOrderCVo.getCouponId());
            if (wxCoupon == null) {
                logger.error("券: coupon-" + wxCoupon.getId() + "不存在" + "couponOrder-" + couponOrderCVo.getId());
                throw new MallinkException(ErrorCode.COUPON_ORDER_IS_NULL);
            }
            try {
                doVerify(retObj, EnumPosActionType.VERIFY_PAY, couponOrderCVo, wxCoupon, merchantId, buUserId, posOrderId);
            } catch (MallinkException e) {
                logger.error(e.getMessage());
                throw new MallinkException(e.getErrorCode(), e.getMessage());
            } catch (Exception e) {
                logger.error(e.getMessage());
                throw new MallinkException(500, e.getMessage());
            }
            retObj.put(WxPayConstant.IS_SELECTED, String.valueOf(EnumPosSelect.SELECTED.getCode()));
            Integer remainAmount = promotionCalc.getAmountLeftAfterPay() - couponOrderCVo.getPrice();
            if (remainAmount < 0) {
                remainAmount = 0;
            }
            Integer discountAmount = promotionCalc.getAmountLeftAfterPay() - remainAmount;
            promotionCalc.setPromotionAmount(discountAmount);
            promotionCalc.setAmountLeftAfterPay(remainAmount);
        }
        return retObj;
    }

    private WxCouponOrderCVo checkAndGetCouponOrder(String couponOrderIdStr, Long merchantId, Date currDate) {
        // 1, 获取券包信息
        WxCouponOrderCVo couponOrderCVo = couponOrderService.detailCUserVo(couponOrderIdStr);
        if (couponOrderCVo == null) {
            logger.error(ErrorCode.COUPON_ORDER_IS_NULL.getMessage() + ": " + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_NULL);
        }
        // 2. 券包状态检查
        if (couponOrderCVo.getCouponOrderStatus() == EnumCouponOrderStatus.COUPON_ORDER_OVER_TIME.getCode()) {
            logger.error("已过期: couponOrder-" + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_OVER_TIME);
        }
        if (couponOrderCVo.getCouponOrderStatus() == EnumCouponOrderStatus.COUPON_ORDER_INVALID.getCode()) {
            logger.error("已作废: couponOrder-" + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_INVALID);
        }
        if (couponOrderCVo.getCouponOrderStatus() == EnumCouponOrderStatus.COUPON_ORDER_USED.getCode()) {
            logger.error("已经核销过的券: couponOrder-" + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_USED);
        }
        // 3. 检查有效期
        if (couponOrderCVo.getValidStartDate() != null && couponOrderCVo.getValidEndDate() != null) {
            if (couponOrderCVo.getValidStartDate().getTime() > currDate.getTime()) {
                logger.error("此券有效期未开始:" + couponOrderIdStr);
                throw new MallinkException(ErrorCode.COUPON_ORDER_IS_EARLIER_THAN_VALIDDATE);
            }
            if (couponOrderCVo.getValidEndDate().getTime() < currDate.getTime()) {
                logger.error("此券有效期已结束:" + couponOrderIdStr);
                throw new MallinkException(ErrorCode.COUPON_ORDER_IS_EARLIER_THAN_VALIDDATE);
            }
        }
        // 4 券已过期
        if (couponOrderCVo.getExpiredTime().before(currDate)) {
            logger.error("此券已过期:" + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_EARLIER_THAN_VALIDDATE);
        }

        Long couponOrderId = couponOrderCVo.getId();

        // 5. 检查适用门店
        WxCouponMerchant wxCouponMerchant = new WxCouponMerchant();
        wxCouponMerchant.setProductId(couponOrderCVo.getCouponId());
        wxCouponMerchant.setStatus(EnumCouponMerchantStatus.COUPON_MERCHANT_STATUS_VALID.getCode());
        List<WxCouponMerchant> couponMerchantList = couponMerchantMapper.findList(wxCouponMerchant);
        if (!couponMerchantList.stream().anyMatch(cm->cm.getMerchantId().equals(merchantId))){
            logger.error("券: coupon-" + couponOrderCVo.getCouponId() + "核销: couponMerchantId-" + merchantId + " 门店不适用");
            throw new MallinkException(ErrorCode.VERIFY_COUPON_ORDER_MERCHANT_IS_NULL);
        }
        return couponOrderCVo;
    }

    private WxCUser getMemUser(String tenantId, String memIdStr, String memPhoneStr) {
        WxCUser user = null;
        if (StringUtils.isNotBlank(memIdStr)) {
            Long memId;
            try {
                memId = Long.valueOf(memIdStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
            }

            user = cUserService.getById(memId);
        }
        if (StringUtils.isNotBlank(memPhoneStr)) {
            WxCUser q = new WxCUser();
            q.setTenantId(tenantId);
            q.setPhone(memPhoneStr);
            user = cUserService.getByObject(q);
        }
        return user;
    }

    /**
     * 会员折扣
     * @param promotionCalc
     * @param discountConfig
     * @param tenantId
     * @param user
     * @param merchantId
     * @return
     */
    private JSONObject getMemLevelDiscount(PromotionCalc promotionCalc, Integer discountConfig, String tenantId, WxCUser user, Long merchantId) {
        JSONObject discountObj = new JSONObject();
        Integer discount = 100;
        if (discountConfig.equals(EnumPosEnableType.Enable.getCode())) {
            String level = WxLevelConfigService.DEFAULT_LEVEL;
            // 1. 会员级别Level
            List<WxLevelConfig> levelList = levelConfigService.getByTenantId(tenantId);
            Long levelId = 0L;
            for (WxLevelConfig levelConfig : levelList) {
                if (user.getScore() >= levelConfig.getPoints()) {
                    if (levelConfig.getDiscountEnable().equals(EnumLevelConfigDiscountStatus.ENABLE.getCode()))
                        levelId = levelConfig.getId();
                    level = levelConfig.getLevel();
                }
            }

            // 2. 会员级别对应折扣discount
            WxLevelMerchant levelMerchant = new WxLevelMerchant();
            levelMerchant.setTenantId(tenantId);
            levelMerchant.setLevelId(levelId);
            levelMerchant.setMerchantId(merchantId);
            List<WxLevelMerchantCVo> levelMerchantList = levelConfigService.findListCVo(levelMerchant);
            if (levelMerchantList.size() >= 1) {
                WxLevelMerchantCVo levelMerchantCVo = levelMerchantList.get(0);
                if (levelMerchantCVo != null) {
                    discount = levelMerchantCVo.getDiscount();
                }
            }

            discountObj.put(WxPayConstant.LEVEL, level);
            discountObj.put(WxPayConstant.DISCOUNT_DES, level);
            discountObj.put(WxPayConstant.DISCOUNT, discount);
        } else {
            discountObj.put(WxPayConstant.DISCOUNT, discount);
            discountObj.put(WxPayConstant.DISCOUNT_DES, "不支持会员折扣");
        }

        // 13. 计算折扣及剩余支付金额(四舍五入)
        Double remainAmountD = promotionCalc.getAmountLeftAfterPay() * 1.0D * discount / 100;
        Integer remainAmount = Long.valueOf(Math.round(remainAmountD)).intValue();
        Integer discountAmount = promotionCalc.getAmountLeftAfterPay() - remainAmount;

        discountObj.put(WxPayConstant.ORDER_AMOUNT, promotionCalc.getOrderAmount());
        discountObj.put(WxPayConstant.ORDER_AMOUNT_LEFT, promotionCalc.getOrderAmountLeft());
        discountObj.put(WxPayConstant.DISCOUNT_AMOUNT, String.valueOf(discountAmount));
        discountObj.put(WxPayConstant.REMAIN_AMOUNT, String.valueOf(remainAmount));
        promotionCalc.setPromotionAmount(discountAmount);
        promotionCalc.setAmountLeftAfterPay(remainAmount);
        return discountObj;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject couponOrderVerify(Map<String, String> params) throws MallinkException {
        JSONObject dataMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID);              // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID);       // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	        // POS操作员ID
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE);         // 交易场景
        String orderIdStr = params.get(WxPayConstant.POS_ORDER_ID);         // POS订单ID
        String orderCreateSN = params.get(WxPayConstant.ORDER_CREATE_SN);   // POS SN号
        String coListStr = params.get(WxPayConstant.COUPON_ORDER_LIST); 	// 优惠券列表

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        } else {
            dataMap.put(WxPayConstant.TENANT_ID, tenantId);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        } else {
            dataMap.put(WxPayConstant.MERCHANT_ID, merchantIdStr);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        } else {
            dataMap.put(WxPayConstant.BUSER_ID, buUserIdStr);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        } else {
            dataMap.put(WxPayConstant.SCENE_TYPE, sceneTypeStr);
        }
        if (StringUtils.isBlank(orderIdStr)) {
            errParam(WxPayConstant.POS_ORDER_ID);
        } else {
            dataMap.put(WxPayConstant.POS_ORDER_ID, orderIdStr);
        }
        if (StringUtils.isBlank(orderCreateSN)) {
            errParam(WxPayConstant.ORDER_CREATE_SN);
        } else {
            dataMap.put(WxPayConstant.ORDER_CREATE_SN, orderCreateSN);
        }

        // 0. 场景
        Integer iSceneType;
        try {
            iSceneType = Integer.valueOf(sceneTypeStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        // 1. POS 支持 配置
        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.POS_CONFIG_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_NOT_FOUND);
        }

        // 2. 商户ID, B端用户ID
        Long merchantId, buUserId, posOrderId;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
            posOrderId = Long.valueOf(orderIdStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        // 3. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 4. check buUser
        WxMerchantBUser buUser = checkAndGetMerchantUser(buUserId, merchantId);

        EnumPosSceneType sceneType = EnumPosSceneType.getEnum(iSceneType);
        JSONObject promotionData = new JSONObject();
        JSONObject promotionInfo = new JSONObject();
        JSONObject promotionPayment = new JSONObject();
        PromotionCalc promotionCalc = new PromotionCalc();
        if (sceneType.equals(EnumPosSceneType.VERIFY)) {
            if (config.getCoupon().equals(EnumPosCouponEnableType.Disable.getCode())) {
                logger.error(ErrorCode.POS_CONFIG_MEM_COUPON_DISABLE.getMessage());
                throw new MallinkException(ErrorCode.POS_CONFIG_MEM_COUPON_DISABLE);
            }
            JSONObject promotionCoupon = new JSONObject();
            JSONArray couponList = new JSONArray();
            if (StringUtils.isBlank(coListStr)) {
                errParam(WxPayConstant.COUPON_ORDER_LIST);
            }
            JSONArray couponOrderArr = JSON.parseArray(coListStr);
            if (couponOrderArr.size() != 1) {
                String errMessage = "核销只能使用一张券";
                logger.error(errMessage);
                throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), errMessage);
            }
            String couponOrderIdStr = couponOrderArr.getString(0);
            if (couponOrderIdStr != null) {
                try {
                    JSONObject couponPromoMap = oneCouponVerify(promotionCalc, couponOrderIdStr, merchantId, buUserId, posOrderId);
                    couponList.add(couponPromoMap);
                } catch (MallinkException e) {
                    throw new MallinkException(e.getErrorCode(), e.getMessage());
                } catch (Exception e) {
                    throw new MallinkException(500, e.getMessage());
                }
            }
            promotionCoupon.put(WxPayConstant.COUPON_ORDER_LIST, couponList);
            // 券优惠总信息
            promotionCoupon.put(WxPayConstant.COUPON_PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
            promotionCoupon.put(WxPayConstant.REMAIN_AMOUNT, promotionCalc.getAmountLeftAfterPay());
            promotionInfo.put(WxPayConstant.COUPON, promotionCoupon);
        } else if(sceneType.equals(EnumPosSceneType.PAY)) {
            JSONObject promotionCoupon = new JSONObject();
            JSONArray couponList = new JSONArray();

            String memIdStr = params.get(WxPayConstant.MEM_ID);                 // 会员ID
            String memPhoneStr = params.get(WxPayConstant.MEM_PHONE);           // 会员手机
            String orderAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);       // POS订单金额(单位:分)
            String orderAmountLeftStr = params.get(WxPayConstant.ORDER_AMOUNT_LEFT);  // POS订单金额(单位:分)
            if (StringUtils.isBlank(orderAmountStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT);
            } else {
                dataMap.put(WxPayConstant.ORDER_AMOUNT, orderAmountStr);
            }
            if (StringUtils.isBlank(orderAmountLeftStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT_LEFT);
            } else {
                dataMap.put(WxPayConstant.ORDER_AMOUNT_LEFT, orderAmountLeftStr);
            }
            if (StringUtils.isBlank(memIdStr) && StringUtils.isBlank(memPhoneStr)) {
                errParam2(WxPayConstant.MEM_ID, WxPayConstant.MEM_PHONE);
            }
            // 10. 获取会员信息
            WxCUser user = getMemUser(tenantId, memIdStr, memPhoneStr);
            if (user == null) {
                logger.error(ErrorCode.USER_NOT_MEMBER.getMessage());
                throw new MallinkException(ErrorCode.USER_NOT_MEMBER);
            }
            if (user != null) {
                dataMap.put(WxPayConstant.MEM_ID, user.getId().toString());
                dataMap.put(WxPayConstant.MEM_PHONE, user.getPhone());
            }
            // 11. 订单金额
            Integer orderAmount, orderAmountLeft;
            try {
                orderAmount = Integer.valueOf(orderAmountStr);
                orderAmountLeft = Integer.valueOf(orderAmountLeftStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
            }
            promotionCalc.setOrderAmount(orderAmount);
            promotionCalc.setOrderAmountLeft(orderAmountLeft);
            promotionCalc.setPromotionAmount(0);
            promotionCalc.setAmountLeftAfterPay(orderAmountLeft);
            // 12. 会员折扣
            JSONObject discountObj = getMemLevelDiscount(promotionCalc, config.getDiscount(), tenantId, user, merchantId);
            promotionInfo.put(WxPayConstant.MEMBER_DISCOUNT, discountObj);
            // 14. 获取用户可用券列表
            if (!config.getCoupon().equals(EnumPosCouponEnableType.Disable.getCode())) {
                Map<String, Object> coQ = new HashMap<>();
                coQ.put("tenantId", tenantId);
                coQ.put("cUserId", user.getId());
                coQ.put("merchantId", merchantId);
                List<WxCouponOrderCVo> avaCoList = couponOrderMapper.findAvailCouponOrder(coQ);
                JSONArray selCoArr = JSONObject.parseArray(coListStr);
                if (config.getCoupon().equals(EnumPosCouponEnableType.SingleEnable.getCode())) {
                    if (selCoArr.size() > 1) {
                        String errMessage = "支付只能使用一张券";
                        logger.error(errMessage);
                        throw new MallinkException(ErrorCode.POS_COUPON_PAY_ERROR.getCode(), errMessage);
                    }
                }
                if (config.getCoupon().equals(EnumPosCouponEnableType.SingleEnable.getCode()) ||
                        config.getCoupon().equals(EnumPosCouponEnableType.MultiEnable.getCode())) {
                    for (int i = 0; i < avaCoList.size(); i++) {
                        WxCouponOrderCVo couponOrderCVo = avaCoList.get(i);
                        boolean selCoupon = isSelCoupon(selCoArr, couponOrderCVo.getId());
                        try {
                            JSONObject couponObj = oneCouponPay(promotionCalc, couponOrderCVo, merchantId, buUserId, posOrderId, selCoupon);
                            couponList.add(couponObj);
                        } catch (MallinkException e) {
                            throw new MallinkException(e.getErrorCode(), e.getMessage());
                        } catch (Exception e) {
                            throw new MallinkException(500, e.getMessage());
                        }
                    }
                }
            }
            promotionCoupon.put(WxPayConstant.COUPON_ORDER_LIST, couponList);
            // 券优惠总信息
            promotionCoupon.put(WxPayConstant.COUPON_PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
            promotionCoupon.put(WxPayConstant.REMAIN_AMOUNT, promotionCalc.getAmountLeftAfterPay());
            promotionInfo.put(WxPayConstant.COUPON, promotionCoupon);
        }
        // 构造支付信息
        promotionPayment.put(WxPayConstant.ORDER_AMOUNT, promotionCalc.getOrderAmount());
        promotionPayment.put(WxPayConstant.ORDER_AMOUNT_LEFT, promotionCalc.getOrderAmountLeft());
        promotionPayment.put(WxPayConstant.PROMOTION_AMOUNT, promotionCalc.getPromotionAmount());
        promotionPayment.put(WxPayConstant.AMOUNT_LEFT_AFTER_PAY, promotionCalc.getAmountLeftAfterPay());
        promotionData.put(WxPayConstant.PAYMENT_PREDICTION, promotionPayment);
        promotionData.put(WxPayConstant.PROMOTION_INFO, promotionInfo);
        dataMap.put(WxPayConstant.PROMOTION_DATA, JSON.toJSONString(promotionData));
        return dataMap;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject couponOrderVerifyCancel(Map<String, String> params) {
        JSONObject retMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID); 		         // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID); 	     // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	         // POS操作员ID
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE);          // 交易场景
        String orderIdStr = params.get(WxPayConstant.POS_ORDER_ID);          // POS订单ID
        String orderCreateSN = params.get(WxPayConstant.ORDER_CREATE_SN);    // POS SN号
        String paymentInfoStr = params.get(WxPayConstant.PAYMENT_INFO);      // 优惠信息

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        } else {
            retMap.put(WxPayConstant.TENANT_ID, tenantId);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        } else {
            retMap.put(WxPayConstant.MERCHANT_ID, merchantIdStr);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        } else {
            retMap.put(WxPayConstant.BUSER_ID, buUserIdStr);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        } else {
            retMap.put(WxPayConstant.SCENE_TYPE, sceneTypeStr);
        }
        if (StringUtils.isBlank(orderIdStr)) {
            errParam(WxPayConstant.POS_ORDER_ID);
        } else {
            retMap.put(WxPayConstant.POS_ORDER_ID, orderIdStr);
        }
        if (StringUtils.isBlank(orderCreateSN)) {
            errParam(WxPayConstant.ORDER_CREATE_SN);
        } else {
            retMap.put(WxPayConstant.ORDER_CREATE_SN, orderCreateSN);
        }
        if (StringUtils.isBlank(paymentInfoStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }

        Integer iSceneType;
        try {
            iSceneType = Integer.valueOf(sceneTypeStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        Long merchantId, buUserId, posOrderId = 0L;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
            posOrderId = Long.valueOf(orderIdStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }

        // 2. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 3. check buUser
        WxMerchantBUser buUser = checkAndGetMerchantUser(buUserId, merchantId);

        JSONObject paymentInfo = JSON.parseObject(paymentInfoStr);
        if (paymentInfo == null) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        String couponOrderIdStr = paymentInfo.getString(WxPayConstant.COUPON_ID);
        if (StringUtils.isBlank(couponOrderIdStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        // 4. check couponOrderId
        WxCouponOrderCVo couponOrderCVo = couponOrderService.detailCUserVo(couponOrderIdStr);
        if (couponOrderCVo == null) {
            logger.error(ErrorCode.COUPON_ORDER_IS_NULL.getMessage() + ": " + couponOrderIdStr);
            throw new MallinkException(ErrorCode.COUPON_ORDER_IS_NULL);
        }
        if (couponOrderCVo.getCouponOrderStatus().equals(EnumCouponOrderStatus.COUPON_ORDER_USE_WAIT.getCode())) {
            logger.error("券包ID: " + couponOrderCVo.getId() + "未核销");
            throw new MallinkException(ErrorCode.POS_COUPON_ORDER_NOT_VERIFY);
        }
        // 5. 检查适用门店
        WxCouponMerchant wxCouponMerchant = new WxCouponMerchant();
        wxCouponMerchant.setProductId(couponOrderCVo.getCouponId());
        wxCouponMerchant.setStatus(EnumCouponMerchantStatus.COUPON_MERCHANT_STATUS_VALID.getCode());
        List<WxCouponMerchant> couponMerchantList = couponMerchantMapper.findList(wxCouponMerchant);
        if (!couponMerchantList.stream().anyMatch(cm->cm.getMerchantId().equals(merchantId))){
            logger.error("券: coupon-" + couponOrderCVo.getCouponId() + "核销: couponMerchantId-" + merchantIdStr + " 门店不适用");
            throw new MallinkException(ErrorCode.VERIFY_COUPON_ORDER_MERCHANT_IS_NULL);
        }
        // 5. 核销后无法反向
        WxCoupon coupon = couponMapper.selectById(couponOrderCVo.getCouponId());
        if (coupon.getSalePrice() > 0 ||
                (coupon.getSubsidyType() > EnumCouponSubsidyType.NO_SUBSIDY.getCode() && coupon.getSubsidyNum() > 0)) {
            logger.error("券包ID: " + couponOrderIdStr + "核销无法退回");
            throw new MallinkException(ErrorCode.POS_COUPON_ORDER_VERIFY_NOT_CANCEL);
        }

        WxCouponOrder couponOrder = new WxCouponOrder();
        couponOrder.setId(couponOrderCVo.getId());
        // 6. 核销撤销
        if (iSceneType.equals(EnumPosSceneType.VERIFY.getCode()) &&
                couponOrderCVo.getCouponOrderStatus().equals(EnumCouponOrderStatus.COUPON_ORDER_USED.getCode())) {
            logger.info("券已被核销无法取消: " + couponOrderIdStr);
            JSONObject refundObj = new JSONObject();
            String errMesg = String.format("此券核销后无法退回, 券%s抵用%s元, 购买花费%s元",
                    couponOrderIdStr, couponOrderCVo.getPriceStr(), couponOrderCVo.getSalePriceStr());
            refundObj.put(WxPayConstant.COUPON_REFUND_ID, couponOrderIdStr);
            refundObj.put(WxPayConstant.COUPON_REFUND_RESULT, "0");
            refundObj.put(WxPayConstant.REFUND_RESULT_MSG, errMesg);
            refundObj.put(WxPayConstant.REFUND_TIME_FROM_IFORMALL, neuDateFormat.format(new Date()));
            refundObj.put(WxPayConstant.PRICE, coupon.getPrice());
            refundObj.put(WxPayConstant.SALE_PRICE, coupon.getSalePrice());
            retMap.put(WxPayConstant.COUPON_REFUND_RESULT, JSON.toJSONString(refundObj));
            return retMap;
            /*
            try {
                cancelVerify(couponOrderCVo, buUserId, posOrderId);
                JSONObject refundObj = new JSONObject();
                refundObj.put(WxPayConstant.COUPON_REFUND_ID, String.valueOf(couponOrderCVo.getId()));
                refundObj.put(WxPayConstant.COUPON_REFUND_RESULT, "0");
                refundObj.put(WxPayConstant.COUPON_REFUND_RESULT_MSG, "核销撤销成功");
                refundObj.put(WxPayConstant.REFUND_TIME_FROM_IFORMALL, neuDateFormat.format(new Date()));
                retMap.put(WxPayConstant.COUPON_REFUND_RESULT, JSON.toJSONString(refundObj));
                return retMap;
            } catch (MallinkException e) {
                logger.error(e.getMessage());
                JSONObject refundObj = new JSONObject();
                refundObj.put(WxPayConstant.COUPON_REFUND_ID, String.valueOf(couponOrderCVo.getId()));
                refundObj.put(WxPayConstant.COUPON_REFUND_RESULT, e.getErrorCode());
                refundObj.put(WxPayConstant.COUPON_REFUND_RESULT_MSG, "核销撤销失败: " + e.getMessage());
                refundObj.put(WxPayConstant.REFUND_TIME_FROM_IFORMALL, neuDateFormat.format(new Date()));
                retMap.put(WxPayConstant.COUPON_REFUND_RESULT, JSON.toJSONString(refundObj));
                return  retMap;
            }
            */
        }
        // 6. 预核销取消
        else if (iSceneType.equals(EnumPosSceneType.PAY.getCode()) &&
                couponOrderCVo.getCouponOrderStatus().equals(EnumCouponOrderStatus.POS_PRE_VERIFY.getCode())) {
            logger.info("券已被预核销: " + couponOrderIdStr);
            if (!couponOrderCVo.getBUserId().equals(buUserId)) {
                logger.error(ErrorCode.VERIFY_PRE_BUSER_NOT_EQUAL.getMessage() + ": " + buUserIdStr);
                throw new MallinkException(ErrorCode.VERIFY_PRE_BUSER_NOT_EQUAL);
            }
            String memIdStr = params.get(WxPayConstant.MEM_ID);                 // 会员ID
            String memPhoneStr = params.get(WxPayConstant.MEM_PHONE);           // 会员手机
            String orderAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);       // POS订单金额(单位:分)
            String orderAmountLeftStr = params.get(WxPayConstant.ORDER_AMOUNT_LEFT);  // POS订单金额(单位:分)
            String promotionAmountStr = paymentInfo.getString(WxPayConstant.PROMOTION_AMOUNT);
            String amountLeftAfterPayStr = paymentInfo.getString(WxPayConstant.AMOUNT_LEFT_AFTER_PAY);
            if (StringUtils.isBlank(orderAmountStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT);
            } else {
                retMap.put(WxPayConstant.ORDER_AMOUNT, orderAmountStr);
            }
            if (StringUtils.isBlank(orderAmountLeftStr)) {
                errParam(WxPayConstant.ORDER_AMOUNT_LEFT);
            } else {
                retMap.put(WxPayConstant.ORDER_AMOUNT_LEFT, orderAmountLeftStr);
            }
            if (StringUtils.isBlank(memIdStr) && StringUtils.isBlank(memPhoneStr)) {
                errParam2(WxPayConstant.MEM_ID, WxPayConstant.MEM_PHONE);
            }
            if (StringUtils.isBlank(promotionAmountStr)) {
                errParam(WxPayConstant.PROMOTION_AMOUNT);
            }
            if (StringUtils.isBlank(amountLeftAfterPayStr)) {
                errParam(WxPayConstant.AMOUNT_LEFT_AFTER_PAY);
            }
            Integer promotionAmount, amountLeftAfterPay;
            try {
                promotionAmount = Integer.valueOf(promotionAmountStr);
                amountLeftAfterPay = Integer.valueOf(amountLeftAfterPayStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(500, e.getMessage());
            }
            if (!couponOrderCVo.getPrice().equals(promotionAmount)) {
                String errMesg = String.format("券%s的金额与退款金额不一致", couponOrderIdStr);
                logger.error(errMesg);
                throw new MallinkException(ErrorCode.POS_COUPON_REFUND_ERROR.getCode(), errMesg);
            }
            // 10. 获取会员信息
            WxCUser user = getMemUser(tenantId, memIdStr, memPhoneStr);
            if (user == null) {
                logger.error(ErrorCode.USER_NOT_MEMBER.getMessage());
                throw new MallinkException(ErrorCode.USER_NOT_MEMBER);
            }
            if (user != null) {
                retMap.put(WxPayConstant.MEM_ID, user.getId().toString());
                retMap.put(WxPayConstant.MEM_PHONE, user.getPhone());
            }

            try {
                cancelPreVerify(couponOrderCVo, posOrderId);
                JSONObject refundObj = new JSONObject();
                refundObj.put(WxPayConstant.COUPON_REFUND_ID, String.valueOf(couponOrderCVo.getId()));
                refundObj.put(WxPayConstant.COUPON_REFUND_RESULT, "0");
                refundObj.put(WxPayConstant.REFUND_RESULT_MSG, "核销撤销成功");
                refundObj.put(WxPayConstant.REFUND_TIME_FROM_IFORMALL, neuDateFormat.format(new Date()));
                refundObj.put(WxPayConstant.PROMOTION_AMOUNT, 0);
                refundObj.put(WxPayConstant.AMOUNT_LEFT_AFTER_PAY, amountLeftAfterPay + couponOrderCVo.getPrice());
                retMap.put(WxPayConstant.COUPON_REFUND_RESULT, JSON.toJSONString(refundObj));
                return retMap;
            } catch (MallinkException e) {
                logger.error(e.getMessage());
                throw new MallinkException(e.getErrorCode(), e.getMessage());
            }
        }

        String errMessage = String.format("核销撤销异常：券%s场景%s核销状态%d,不可撤销", couponOrderIdStr, sceneTypeStr, couponOrderCVo.getCouponOrderStatus());
        logger.error(errMessage);
        throw new MallinkException(ErrorCode.POS_COUPON_REFUND_ERROR.getCode(), errMessage);
    }

    /**
     * 预核销
     * @param buUserId
     * @param couponOrderCVo
     * @param posOrderId
     */
    private void doPreVerify(WxCouponOrderCVo couponOrderCVo, Long buUserId, Long posOrderId) {
        int num = 0;
        // 1. insert posOrderId
        PosCouponOrderVerify posCouponOrderVerify = new PosCouponOrderVerify();
        posCouponOrderVerify.setTenantId(couponOrderCVo.getTenantId());
        posCouponOrderVerify.setCouponOrderId(couponOrderCVo.getId());
        posCouponOrderVerify.setPosOrderId(posOrderId);
        posCouponOrderVerify.setState(EnumEnableType.Enable.getCode());
        posCouponOrderVerify.setCreateDate(new Date());
        posCouponOrderVerify.setUpdateDate(new Date());
        try {
            posCouponOrderVerifyService.saveOrUpdate(posCouponOrderVerify);
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }

        // 2. update couponOrder
        try {
            WxCouponOrder couponOrder = new WxCouponOrder();
            couponOrder.setId(couponOrderCVo.getId());
            couponOrder.setTenantId(couponOrderCVo.getTenantId());
            couponOrder.setCouponOrderStatus(EnumCouponOrderStatus.POS_PRE_VERIFY.getCode());
            couponOrder.setBUserId(buUserId);
            couponOrder.setVerifyType(EnumCouponVerifyType.VERIFY_POS_PAY.getCode());
            couponOrder.setUpdateDate(new Date());
            num = couponOrderMapper.updateVerifyCouponOrder(couponOrder);
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }
        if (num != 1) {
            logger.error("预核销异常：updatete num " + num);
            throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), "updatete num " + num);
        }
        logger.info("预核销已完成: " +  couponOrderCVo.getId());
    }

    /**
     * 预核销回退
     * @param couponOrderCVo
     * @param posOrderId
     */
    private void cancelPreVerify(WxCouponOrderCVo couponOrderCVo, Long posOrderId) {
        int num = 0;
        // 1. update posOrderId
        PosCouponOrderVerify posCouponOrderVerify = new PosCouponOrderVerify();
        posCouponOrderVerify.setTenantId(couponOrderCVo.getTenantId());
        posCouponOrderVerify.setCouponOrderId(couponOrderCVo.getId());
        posCouponOrderVerify.setPosOrderId(posOrderId);
        posCouponOrderVerify.setState(EnumEnableType.Enable.getCode());
        List<PosCouponOrderVerify> posCouponOrderVerifyList = posCouponOrderVerifyService.getList(posCouponOrderVerify);
        if (posCouponOrderVerifyList.size() == 1) {
            posCouponOrderVerify = posCouponOrderVerifyList.get(0);
            if(posCouponOrderVerify != null) {
                try {
                    posCouponOrderVerifyService.deleteById(posCouponOrderVerify.getId());
                } catch (Exception e) {
                    logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
                    throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
                }
            }
        } else {
            logger.error(ErrorCode.VERIFY_PRE_POS_ORDER_NOT_EQUAL.getMessage() + ": " + posOrderId);
            throw new MallinkException(ErrorCode.VERIFY_PRE_POS_ORDER_NOT_EQUAL);
        }

        // 2. update couponOrder
        try {
            WxCouponOrder couponOrder = new WxCouponOrder();
            couponOrder.setId(couponOrderCVo.getId());
            couponOrder.setTenantId(couponOrderCVo.getTenantId());
            couponOrder.setCouponOrderStatus(EnumCouponOrderStatus.COUPON_ORDER_USE_WAIT.getCode());
            couponOrder.setUpdateDate(new Date());
            num = couponOrderMapper.cancelVerifyCouponOrder(couponOrder);
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }
        if (num != 1) {
            logger.error("预核销异常：updatete num " + num);
            throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), "updatete num " + num);
        }
        logger.info("预核销已取消: " +  couponOrderCVo.getId());
    }

    /**
     * 核销
     * @param couponOrderCVo
     * @param merchantId
     * @param buUserId
     * @param posOrderId
     */
    private void doVerify(JSONObject retMap, EnumPosActionType actionType, WxCouponOrderCVo couponOrderCVo, WxCoupon coupon,
                          Long merchantId, Long buUserId, Long posOrderId) {
        int num = 0;
        // 1. insert posOrderId
        PosCouponOrderVerify posCouponOrderVerify = new PosCouponOrderVerify();
        posCouponOrderVerify.setTenantId(couponOrderCVo.getTenantId());
        posCouponOrderVerify.setCouponOrderId(couponOrderCVo.getId());
        posCouponOrderVerify.setPosOrderId(posOrderId);
        posCouponOrderVerify.setState(EnumEnableType.Enable.getCode());
        try {
            posCouponOrderVerifyService.saveOrUpdate(posCouponOrderVerify);
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }
        // 2. update couponOrder
        Date curDate = new Date();
        try {
            WxCouponOrder couponOrder = new WxCouponOrder();
            couponOrder.setId(couponOrderCVo.getId());
            couponOrder.setTenantId(couponOrderCVo.getTenantId());
            if (actionType.equals(EnumPosActionType.VERIFY_INDEPENT)) {
                couponOrder.setVerifyType(EnumCouponVerifyType.VERIFY_POS_INDEPENDENT.getCode());
                couponOrder.setCouponOrderStatus(EnumCouponOrderStatus.COUPON_ORDER_USED.getCode());
            } else {
                couponOrder.setVerifyType(EnumCouponVerifyType.VERIFY_POS_PAY.getCode());
                couponOrder.setCouponOrderStatus(EnumCouponOrderStatus.POS_PRE_VERIFY.getCode());
            }
            couponOrder.setBUserId(buUserId);
            couponOrder.setUpdateDate(curDate);
            if (couponOrderCVo.getBUserId() == null) {
                num = couponOrderMapper.updateVerifyCouponOrder(couponOrder);
            } else {
                num = couponOrderMapper.updateVerifyCouponOrderStatus(couponOrder);
            }
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }
        if (num != 1) {
            logger.error("核销异常：update num " + num);
            throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), "updatete num " + num);
        }
        logger.info("核销已完成: " +  couponOrderCVo.getId());
    }

    private int posAddCredit(WxCouponOrderCVo couponOrderCVo, WxCoupon coupon, Long merchantId, Long buUserId) {
        try {
            //-------此处为【现金支付】记录增加积分操作-------
            WxCreditHistory creditHistory = new WxCreditHistory();
            creditHistory.setOperatorType(EnumUserType.BUSER.getCode());
            creditHistory.setOperatorId(buUserId);
            creditHistory.setCUserId(couponOrderCVo.getcUserId());
            creditHistory.setCreateDate(new Date());
            creditHistory.setTenantId(couponOrderCVo.getTenantId());
            creditHistory.setCreditType(EnumScoreType.CONSUMPTION.getCode());
            creditHistory.setCouponId(couponOrderCVo.getCouponId());
            creditHistory.setBusinessId(coupon.getBusiness());
            creditHistory.setSpend(couponOrderCVo.getCouponPrice());
            //如果券与商户一对一 则直接将消费商户更新为此商户 若一对多 则消费商户显示多商户
            creditHistory.setMerchantId(merchantId);
            creditHistory = creditHistoryService.saveOrUpdate(creditHistory);
            if (creditHistory.getCreditNum() != null) {
                return creditHistory.getCreditNum();
            } else {
                return 0;
            }
        } catch (Exception e) {
            logger.error("核销积分值异常:" + e.getMessage());
        }
        return 0;
    }

    private int posAddCreditForPay(WxMerchant merchant, Long buUserId, Long memId, Integer payment) {
        try {
            //-------此处为【现金支付】记录增加积分操作-------
            WxCreditHistory creditHistory = new WxCreditHistory();
            creditHistory.setOperatorType(EnumUserType.BUSER.getCode());
            creditHistory.setOperatorId(buUserId);
            creditHistory.setCUserId(memId);
            creditHistory.setCreateDate(new Date());
            creditHistory.setTenantId(merchant.getTenantId());
            creditHistory.setCreditType(EnumScoreType.CONSUMPTION.getCode());
            creditHistory.setBusinessId(merchant.getBusinessId());
            creditHistory.setSpend(payment);
            //如果券与商户一对一 则直接将消费商户更新为此商户 若一对多 则消费商户显示多商户
            creditHistory.setMerchantId(merchant.getId());
            creditHistory = creditHistoryService.saveOrUpdate(creditHistory);
            if (creditHistory.getCreditNum() != null) {
                return creditHistory.getCreditNum();
            } else {
                return 0;
            }
        } catch (Exception e) {
            logger.error("核销积分值异常:" + e.getMessage());
        }
        return 0;
    }

    /**
     * 核销回退
     * @param couponOrderCVo
     * @param buUserId
     * @param posOrderId
     */
    private void cancelVerify(WxCouponOrderCVo couponOrderCVo, Long buUserId, Long posOrderId) {
        // 1. 积分历史回退
        try {
            WxCreditHistory creditHistory = new WxCreditHistory();
            creditHistory.setOperatorType(EnumUserType.BUSER.getCode());
            creditHistory.setOperatorId(buUserId);
            creditHistory.setCUserId(couponOrderCVo.getcUserId());
            creditHistory.setTenantId(couponOrderCVo.getTenantId());
            creditHistory.setCreditType(EnumScoreType.CONSUMPTION.getCode());
            creditHistory.setCouponId(couponOrderCVo.getCouponId());
            int num = creditHistoryService.cancelCredit(creditHistory);
            if (num <= 1) {
                logger.warn("核销积分历史回退: " + creditHistory.toString());
            }
        } catch (Exception e) {
            String errMessage = "核销积分历史回退:" + e.getMessage();
            logger.error(errMessage);
            throw new MallinkException(500, errMessage);
        }
        // 2. 成长值历史回退
        try {
            WxScoreHistory scoreHistory = new WxScoreHistory();
            scoreHistory.setTenantId(couponOrderCVo.getTenantId());
            scoreHistory.setScoreType(EnumScoreType.CONSUMPTION.getCode());
            scoreHistory.setCUserId(couponOrderCVo.getcUserId());
            scoreHistory.setOrderId(couponOrderCVo.getOrderId());
            int num = scoreRulesService.cancelScore(scoreHistory);
            if (num <= 1) {
                logger.warn("核销成长值历史回退: " + scoreHistory.toString());
            }
        } catch (Exception e) {
            String errMessage = "核销成长值历史回退:" + e.getMessage();
            logger.error(errMessage);
            throw new MallinkException(500, errMessage);
        }

        // 3. update posOrderId
        PosCouponOrderVerify posCouponOrderVerify = new PosCouponOrderVerify();
        posCouponOrderVerify.setTenantId(couponOrderCVo.getTenantId());
        posCouponOrderVerify.setCouponOrderId(couponOrderCVo.getId());
        posCouponOrderVerify.setPosOrderId(posOrderId);
        posCouponOrderVerify.setState(EnumEnableType.Enable.getCode());
        List<PosCouponOrderVerify> posCouponOrderVerifyList = posCouponOrderVerifyService.getList(posCouponOrderVerify);
        if (posCouponOrderVerifyList.size() == 1) {
            posCouponOrderVerify = posCouponOrderVerifyList.get(0);
            if (posCouponOrderVerify != null) {
                try {
                    posCouponOrderVerifyService.deleteById(posCouponOrderVerify.getId());
                } catch(Exception e){
                    logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
                    throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
                }
            }
        } else {
            logger.error(ErrorCode.VERIFY_POS_ORDER_NOT_EQUAL.getMessage() + ": " + posOrderId);
            throw new MallinkException(ErrorCode.VERIFY_POS_ORDER_NOT_EQUAL);
        }
        // 4. update couponOrder
        int num = 0;
        try {
            WxCouponOrder couponOrder = new WxCouponOrder();
            couponOrder.setId(couponOrderCVo.getId());
            couponOrder.setTenantId(couponOrderCVo.getTenantId());
            couponOrder.setCouponOrderStatus(EnumCouponOrderStatus.COUPON_ORDER_USE_WAIT.getCode());
            couponOrder.setUpdateDate(new Date());
            num = couponOrderMapper.cancelVerifyCouponOrder(couponOrder);
        } catch (Exception e) {
            logger.error("db failed: couponOrder-" + couponOrderCVo.getId() + ", e:" + e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL.getCode(), "DB FAILD " + e.getMessage());
        }
        if (num != 1) {
            logger.error("核销异常：updatete num " + num);
            throw new MallinkException(ErrorCode.VERIFY_ERROR.getCode(), "updatete num " + num);
        }
        logger.info("核销已取消: " +  couponOrderCVo.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject cardPayPre(Map<String, String> params) throws MallinkException {
        JSONObject retMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID); 		         // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID); 	     // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	         // POS操作员ID
        String memIdStr = params.get(WxPayConstant.MEM_ID);                 // 会员ID
        String memPhoneStr = params.get(WxPayConstant.MEM_PHONE);           // 会员手机
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE);          // 交易场景
        String posOrderIdStr = params.get(WxPayConstant.POS_ORDER_ID);       // POS订单ID
        String orderAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);          // POS订单总额(单位:分)
        String orderAmountLeftStr = params.get(WxPayConstant.ORDER_AMOUNT_LEFT); // 本次需要支付的订单金额(单位:分)
        String cardIdStr = params.get(WxPayConstant.CARD_ID);                    // 消费卡ID
        String amountByCardStr = params.get(WxPayConstant.AMOUNT_PAID_BY_CARD);  // 预付卡支付金额

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        } else {
            retMap.put(WxPayConstant.TENANT_ID, tenantId);
        }
        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.POS_CONFIG_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_NOT_FOUND);
        }
        if (config.getCard().equals(EnumPosEnableType.Disable.getCode())) {
            logger.error(ErrorCode.POS_CONFIG_MEM_CARD_DISABLE.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_MEM_CARD_DISABLE);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        } else {
            retMap.put(WxPayConstant.MERCHANT_ID, merchantIdStr);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        } else {
            retMap.put(WxPayConstant.BUSER_ID, buUserIdStr);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        } else {
            retMap.put(WxPayConstant.SCENE_TYPE, sceneTypeStr);
        }
        if (!sceneTypeStr.equals(EnumPosSceneType.PAY.getCode().toString())) {
            logger.error(ErrorCode.POS_CARD_ONLY_SUPPORT_PAY.getMessage());
            throw new MallinkException(ErrorCode.POS_CARD_ONLY_SUPPORT_PAY);
        }
        if (StringUtils.isBlank(posOrderIdStr)) {
            errParam(WxPayConstant.POS_ORDER_ID);
        } else {
            retMap.put(WxPayConstant.POS_ORDER_ID, posOrderIdStr);
        }
        if (StringUtils.isBlank(orderAmountStr)) {
            errParam(WxPayConstant.ORDER_AMOUNT);
        } else {
            retMap.put(WxPayConstant.ORDER_AMOUNT, orderAmountStr);
        }
        if (StringUtils.isBlank(orderAmountLeftStr)) {
            errParam(WxPayConstant.ORDER_AMOUNT_LEFT);
        } else {
            retMap.put(WxPayConstant.ORDER_AMOUNT_LEFT, orderAmountLeftStr);
        }
        if (StringUtils.isBlank(cardIdStr)) {
            errParam(WxPayConstant.CARD_ID);
        } else {
            retMap.put(WxPayConstant.CARD_ID, cardIdStr);
        }
        if (StringUtils.isBlank(amountByCardStr)) {
            errParam(WxPayConstant.AMOUNT_PAID_BY_CARD);
        } else {
            retMap.put(WxPayConstant.AMOUNT_PAID_BY_CARD, amountByCardStr);
        }

        Long merchantId, buUserId, posOrderId, cardId;
        Integer amountByCard = 0;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
            posOrderId = Long.valueOf(posOrderIdStr);
            amountByCard = Integer.valueOf(amountByCardStr);
            cardId = Long.valueOf(cardIdStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
        }
        // 2. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 3. check buUser
        WxMerchantBUser buUser = checkAndGetMerchantUser(buUserId, merchantId);

        WxCouponOrder couponOrder = couponOrderService.getById(cardId);
        if(couponOrder == null) {
            logger.error("卡不存在: " + cardIdStr);
            throw new MallinkException(ErrorCode.CARD_IS_NOT_FOUND);
        }
        if (StringUtils.isNotBlank(memIdStr) || StringUtils.isNotBlank(memPhoneStr)) {
            WxCUser cuUser = getMemUser(tenantId, memIdStr, memPhoneStr);
            if (cuUser == null) {
                logger.error(ErrorCode.USER_NOT_MEMBER.getMessage());
                throw new MallinkException(ErrorCode.USER_NOT_MEMBER);
            }
            if (couponOrder.getOwnerId().equals(cuUser.getId())) {
                logger.error(ErrorCode.POS_CARD_OWNER_NOT_EQUAL.getMessage());
                throw new MallinkException(ErrorCode.POS_CARD_OWNER_NOT_EQUAL);
            }
        }

        WxCardInfo cardInfo = checkAndGetCardinfo(cardId, merchantId);

        if(amountByCard > cardInfo.getRemainingAmount()) {
            logger.error("卡余额不足: " + cardIdStr);
            throw new MallinkException(ErrorCode.CARD_REMAIN_AMOUNT_IS_NOT_ENOUGH);
        }

        WxCardSpend record = new WxCardSpend();
        record.setPayFrom(EnumCardSpendFrom.POS.getCode());
        record.setTenantId(merchant.getTenantId());
        record.setCardId(cardId);
        record.setOwnerId(couponOrder.getOwnerId());
        record.setMerchantId(merchantId);
        record.setPosOrderId(posOrderId);
        record.setDeductionAmount(amountByCard);

        try {
            WxCardSpend cardSpend = cardSpendService.cardSpendForPosPrePay(record);
            JSONObject cardPaymentObj = new JSONObject();
            cardPaymentObj.put(WxPayConstant.CARD_ID, cardId);
            cardPaymentObj.put(WxPayConstant.CARD_AMOUNT_BEFORE_PAY, cardSpend.getCardBeforeAmount());
            cardPaymentObj.put(WxPayConstant.PAYMENT_AMOUNT, cardSpend.getPayment());
            cardPaymentObj.put(WxPayConstant.CARD_AMOUNT_AFTER_PAY, cardSpend.getCardRemainAmount());
            cardPaymentObj.put(WxPayConstant.CARD_SPEND_ID, cardSpend.getId().toString());
            cardPaymentObj.put(WxPayConstant.CARD_SPEND_TIME, neuDateFormat.format(cardSpend.getCreateDate()));
            retMap.put(WxPayConstant.CARD_PAYMENT_DATA, JSON.toJSONString(cardPaymentObj));
        } catch (MallinkException e) {
            logger.error("card spend error, req 2: " + record.toString() + ", e:" + e.getMessage());
            throw new MallinkException(e.getErrorCode(), e.getMessage());
        } catch (Exception e) {
            logger.error("card spend error, req 3: " + record.toString() + ", e:" + e.getMessage());
            throw new MallinkException(500, e.getMessage());
        }

        return retMap;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject cardPayPreCancel(@RequestBody Map<String, String> params) throws MallinkException {
        JSONObject retMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID); 		         // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID); 	     // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	         // POS操作员ID
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE);          // 交易场景
        String orderIdStr = params.get(WxPayConstant.POS_ORDER_ID);          // POS订单ID
        String orderAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);      // POS订单总额(单位:分)
        String orderAmountLeftStr = params.get(WxPayConstant.ORDER_AMOUNT_LEFT); // 本次需要支付的剩余金额
        String orderCreateSN = params.get(WxPayConstant.ORDER_CREATE_SN);    // POS SN号
        String paymentInfoStr = params.get(WxPayConstant.PAYMENT_INFO);      // 优惠信息

        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        } else {
            retMap.put(WxPayConstant.TENANT_ID, tenantId);
        }
        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.POS_CONFIG_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_NOT_FOUND);
        }
        if (config.getCard().equals(EnumPosEnableType.Disable.getCode())) {
            logger.error(ErrorCode.POS_CONFIG_MEM_CARD_DISABLE.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_MEM_CARD_DISABLE);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        } else {
            retMap.put(WxPayConstant.MERCHANT_ID, merchantIdStr);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        } else {
            retMap.put(WxPayConstant.BUSER_ID, buUserIdStr);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        } else {
            retMap.put(WxPayConstant.SCENE_TYPE, sceneTypeStr);
        }
        if (!sceneTypeStr.equals(EnumPosSceneType.PAY.getCode().toString())) {
            logger.error(ErrorCode.POS_CARD_ONLY_SUPPORT_PAY.getMessage());
            throw new MallinkException(ErrorCode.POS_CARD_ONLY_SUPPORT_PAY);
        }
        if (StringUtils.isBlank(orderIdStr)) {
            errParam(WxPayConstant.POS_ORDER_ID);
        } else {
            retMap.put(WxPayConstant.POS_ORDER_ID, orderIdStr);
        }
        if (StringUtils.isBlank(orderAmountStr)) {
            errParam(WxPayConstant.ORDER_AMOUNT);
        } else {
            retMap.put(WxPayConstant.ORDER_AMOUNT, orderAmountStr);
        }
        if (StringUtils.isBlank(orderAmountLeftStr)) {
            errParam(WxPayConstant.ORDER_AMOUNT_LEFT);
        } else {
            retMap.put(WxPayConstant.ORDER_AMOUNT_LEFT, orderCreateSN);
        }
        if (StringUtils.isBlank(orderCreateSN)) {
            errParam(WxPayConstant.ORDER_CREATE_SN);
        } else {
            retMap.put(WxPayConstant.ORDER_CREATE_SN, orderAmountLeftStr);
        }
        if (StringUtils.isBlank(paymentInfoStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }

        JSONObject paymentInfo = JSON.parseObject(paymentInfoStr);
        if (paymentInfo == null) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        String cardIdStr = paymentInfo.getString(WxPayConstant.CARD_ID);
        if (StringUtils.isBlank(cardIdStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        String cardSpendIdStr = paymentInfo.getString(WxPayConstant.CARD_SPEND_ID);
        if (StringUtils.isBlank(cardSpendIdStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        String amountPaidByCardStr = paymentInfo.getString(WxPayConstant.AMOUNT_PAID_BY_CARD);
        if (StringUtils.isBlank(amountPaidByCardStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }
        String amountLeftAfterPayStr = paymentInfo.getString(WxPayConstant.AMOUNT_LEFT_AFTER_PAY);
        if (StringUtils.isBlank(amountLeftAfterPayStr)) {
            errParam(WxPayConstant.PAYMENT_INFO);
        }

        Long merchantId, buUserId, posOrderId, cardId, cardSpendId;
        Integer amountPaidByCard, amountLeftAfterPay;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
            posOrderId = Long.valueOf(orderIdStr);
            cardId = Long.valueOf(cardIdStr);
            cardSpendId = Long.valueOf(cardSpendIdStr);
            amountPaidByCard = Integer.valueOf(amountLeftAfterPayStr);
            amountLeftAfterPay = Integer.valueOf(amountLeftAfterPayStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
        }
        // 2. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 3. check buUser
        WxMerchantBUser buUser = checkAndGetMerchantUser(buUserId, merchantId);

        WxCouponOrder couponOrder = couponOrderService.getById(cardId);
        if(couponOrder == null) {
            logger.error("卡不存在: " + cardIdStr);
            throw new MallinkException(ErrorCode.CARD_IS_NOT_FOUND);
        }

        WxCardInfo cardInfo = checkAndGetCardinfo(cardId, merchantId);

        WxCardSpend record = cardSpendService.getById(cardSpendId);
        if (record == null) {
            logger.error(ErrorCode.CARD_SPEND_IS_NOT_FOUND.getMessage() + ": " + cardSpendIdStr);
            throw new MallinkException(ErrorCode.CARD_SPEND_IS_NOT_FOUND);
        }
        if (!record.getPayStatus().equals(EnumCardSpendStatus.PREV_PAY.getCode())) {
            logger.error(ErrorCode.POS_CARD_PAY_NOT_CANCEL.getMessage());
            throw new MallinkException(ErrorCode.POS_CARD_PAY_NOT_CANCEL);
        }

        try {
            WxCardInfo updateCardInfo = cardSpendService.cardSpendForPosPrePayCancel(cardInfo, record);
            JSONObject refundObj = new JSONObject();
            refundObj.put(WxPayConstant.CARD_ID, cardIdStr);
            refundObj.put(WxPayConstant.CARD_REFUND_RESULT, "0");
            refundObj.put(WxPayConstant.REFUND_RESULT_MSG, "卡支付撤销成功");
            refundObj.put(WxPayConstant.REFUND_TIME_FROM_IFORMALL, neuDateFormat.format(new Date()));
            refundObj.put(WxPayConstant.AMOUNT_PAID_BY_CARD, 0);
            refundObj.put(WxPayConstant.AMOUNT_LEFT_AFTER_PAY, amountLeftAfterPay + record.getDeductionAmount());
            retMap.put(WxPayConstant.CARD_REFUND_RESULT, JSON.toJSONString(refundObj));
        } catch (MallinkException e) {
            logger.error("card spend error, req 2: " + record.toString() + ", e:" + e.getMessage());
            throw new MallinkException(e.getErrorCode(), e.getMessage());
        } catch (Exception e) {
            logger.error("card spend error, req 3: " + record.toString() + ", e:" + e.getMessage());
            throw new MallinkException(500, e.getMessage());
        }

        return retMap;
    }

    private WxCardInfo checkAndGetCardinfo(Long cardId, Long merchantId) {
        WxCardInfo cardInfo = cardInfoService.getById(cardId);
        if(cardInfo == null) {
            String errMessage = "卡不存在: " + cardId;
            logger.error(errMessage);
            throw new MallinkException(ErrorCode.CARD_IS_NOT_FOUND.getCode(), errMessage);
        }

        WxCouponMerchant couponMerchant = cardSpendService.checkMerchantInCoupon(cardInfo, merchantId);
        if(couponMerchant == null) {
            String errMessage = "商户不支持此卡 " + cardId;
            logger.error(errMessage);
            throw new MallinkException(ErrorCode.COUPON_ORDER_MERANT_IS_NULL.getCode(), errMessage);
        }
        return cardInfo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JSONObject posOrderSync(Map<String, String> params) throws MallinkException {
        JSONObject retMap = new JSONObject();

        String tenantId = params.get(WxPayConstant.TENANT_ID); 		         // 租户ID
        String merchantIdStr = params.get(WxPayConstant.MERCHANT_ID); 	     // 商户ID
        String buUserIdStr = params.get(WxPayConstant.BUSER_ID); 	         // POS操作员ID
        String cuUserIdStr = params.get(WxPayConstant.MEM_ID); 	             // 富茂系统会员ID
        String cuPhoneStr = params.get(WxPayConstant.MEM_PHONE); 	         // 富茂系统会员手机号
        String sceneTypeStr = params.get(WxPayConstant.SCENE_TYPE); 	 // 场景类型
        String posOrderIdStr = params.get(WxPayConstant.POS_ORDER_ID);           // POS订单ID
        String posOrderTimeStr = params.get(WxPayConstant.POS_ORDER_TIME);       // POS订单时间
        String posAmountStr = params.get(WxPayConstant.ORDER_AMOUNT);        // POS订单总额
        String orderCreateSN = params.get(WxPayConstant.ORDER_CREATE_SN);    // POS SN号
        String posStatusStr = params.get(WxPayConstant.ORDER_STATUS);        // 订单状态: 0有效,1取消,根据订单状态处理优惠券;有效：优惠券从预核销处理为核销;

        // 1. check params
        if (StringUtils.isBlank(tenantId)) {
            errParam(WxPayConstant.TENANT_ID);
        }
        PosMallConfig config = posMallConfigService.getByTenantId(tenantId);
        if (config == null) {
            logger.error(ErrorCode.POS_CONFIG_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_CONFIG_NOT_FOUND);
        }
        if (StringUtils.isBlank(merchantIdStr)) {
            errParam(WxPayConstant.MERCHANT_ID);
        }
        if (StringUtils.isBlank(buUserIdStr)) {
            errParam(WxPayConstant.BUSER_ID);
        }
        if (StringUtils.isBlank(cuUserIdStr)) {
            errParam(WxPayConstant.MEM_ID);
        }
        if (StringUtils.isBlank(sceneTypeStr)) {
            errParam(WxPayConstant.SCENE_TYPE);
        }
        if (StringUtils.isBlank(posOrderIdStr)) {
            errParam(WxPayConstant.POS_ORDER_ID);
        }
        if (StringUtils.isBlank(posOrderTimeStr)) {
            errParam(WxPayConstant.POS_ORDER_TIME);
        }
        if (StringUtils.isBlank(posAmountStr)) {
            errParam(WxPayConstant.ORDER_AMOUNT);
        }
        if (StringUtils.isBlank(orderCreateSN)) {
            errParam(WxPayConstant.ORDER_CREATE_SN);
        }
        if (StringUtils.isBlank(posStatusStr)) {
            errParam(WxPayConstant.ORDER_STATUS);
        }

        Long merchantId, buUserId, posOrderId, memId;
        Integer posAmount;
        Integer iSceneType;
        Date orderTime = null;
        try {
            merchantId = Long.valueOf(merchantIdStr);
            buUserId = Long.valueOf(buUserIdStr);
            memId = Long.valueOf(cuUserIdStr);
            iSceneType = Integer.valueOf(sceneTypeStr);
            posOrderId = Long.valueOf(posOrderIdStr);
            posAmount = Integer.valueOf(posAmountStr);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
        }
        try {
            orderTime = neuDateFormat.parse(posOrderTimeStr);
        } catch (ParseException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
        }
        // 2. check merchant
        WxMerchant merchant = checkAndGetMerchant(merchantId);
        // 3. check buUser
        WxMerchantBUser merchantBUser = checkAndGetMerchantUser(buUserId, merchantId);
        // 4. 同步
        if (posStatusStr.equalsIgnoreCase(WxPayConstant.NEU_ORDER_PAY)) {
            // 4.1 支付同步
            syncOrder(params, merchant, merchantBUser, iSceneType, posOrderId, memId, posAmount, orderTime, orderCreateSN);
        } else if (posStatusStr.equalsIgnoreCase(WxPayConstant.NEU_ORDER_REFUND)) {
            // 退款同步
            syncRefundOrder(params, merchantBUser, iSceneType, posOrderId, memId, posAmount, orderTime, orderCreateSN);
        }

        return retMap;
    }

    /**
     * 支付同步
     * @param params
     * @param merchant
     * @param buUser
     * @param iSceneType
     * @param posOrderId
     * @param memId
     * @param orderAmount
     * @param orderTime
     */
    public void syncOrder(
            Map<String, String> params,
            WxMerchant merchant, WxMerchantBUser buUser,
            Integer iSceneType,
            Long posOrderId, Long memId, Integer orderAmount, Date orderTime, String orderCreateSn) {
        WxOrder order = createOrderByPos(buUser, memId, posOrderId, orderAmount, orderTime, orderCreateSn);
        String payDetail = params.get(WxPayConstant.PAY_DETAIL);
        JSONObject payDetailObj = JSON.parseObject(payDetail);
        PromotionCalc scoreCreditCalc = new PromotionCalc();
        scoreCreditCalc.setScore(0);
        scoreCreditCalc.setCredit(0);
        JSONArray payDetailList = payDetailObj.getJSONArray(WxPayConstant.PAY_DETAIL_LIST);
        for (int i=0;i<payDetailList.size();i++) {
            JSONObject payOrderObj = payDetailList.getJSONObject(i);
            // ADD to wx_pay_order
            String neuPayOrderTypeStr = payOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_TYPE);
            String neuPayOrderIdStr = payOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_ID);
            String neuPaymentAmountStr = payOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_AMOUNT);
            String neuPayOrderTimeStr = payOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_TIME);
            String neuPayThirdNo = payOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_OUT_NO);
            Date neuPosPayOrderTime = null;
            try {
                neuPosPayOrderTime = neuDateFormat.parse(neuPayOrderTimeStr);
            } catch (ParseException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
            }
            Long neuPayOrderId;
            Integer neuPayOrderType, neuPaymentAmount;
            try {
                neuPayOrderId = Long.valueOf(neuPayOrderIdStr);
                neuPayOrderType = Integer.valueOf(neuPayOrderTypeStr);
                neuPaymentAmount = Integer.valueOf(neuPaymentAmountStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
            }
            if (neuPayOrderType.equals(EnumNeuPosPamentType.MEM_COUPON.getCode())) {
                // 优惠券
                WxPayOrder payOrder = payOrderFromCouponVerify(
                        scoreCreditCalc,
                        buUser, order, memId, iSceneType,
                        neuPayOrderId, neuPaymentAmount, neuPosPayOrderTime, neuPayThirdNo);
            } else if (neuPayOrderType.equals(EnumNeuPosPamentType.MEM_CARD.getCode())) {
                // 预付卡
                WxPayOrder payOrder = payOrderFromCard(
                        scoreCreditCalc,
                        merchant, buUser, order, memId,
                        neuPayOrderId, neuPaymentAmount, neuPosPayOrderTime, neuPayThirdNo);
            } else {
                // 其它
                payOrderFromPos(scoreCreditCalc,
                        merchant, buUser, order, memId,
                        neuPayOrderType, neuPayOrderId, neuPaymentAmount, neuPosPayOrderTime, neuPayThirdNo);
            }
        }
    }

    /**
     * 退款同步
     * @param params
     * @param buUser
     * @param posOrderId
     * @param memId
     * @param orderAmount
     * @param orderTime
     */
    public void syncRefundOrder(
            Map<String, String> params,
            WxMerchantBUser buUser,
            Integer iSceneType,
            Long posOrderId, Long memId, Integer orderAmount, Date orderTime, String orderCreateSn) {
        // add to wx_order
        WxOrder refundOrder = createRefundByPos(buUser, memId, posOrderId, orderAmount, orderTime, orderCreateSn);
        String refundDetail = params.get(WxPayConstant.NEU_REFUND_DETAIL);
        JSONObject refundDetailObj = JSON.parseObject(refundDetail);
        JSONArray refundDetailList = refundDetailObj.getJSONArray(WxPayConstant.NEU_REFUND_DETAIL_LIST);
        for (int i=0;i<refundDetailList.size();i++) {
            JSONObject refundPayOrderObj = refundDetailList.getJSONObject(i);
            // ADD to wx_pay_order
            String neuRefundOrderTypeStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_TYPE);
            String neuPayOrderIdStr = refundPayOrderObj.getString(WxPayConstant.NEU_PAY_ORDER_ID);
            String neuRefundOrderIdStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_ID);
            String neuRefundOrderAmountStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_AMOUNT);
            String neuRefundOrderTimeStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_TIME);
            String neuRefundOrderStatusStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_STATUS);
            String neuRefundTypeStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_TYPE);
            String neuRefundThirdNo = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_OUT_NO);
            String neuRefundThirdTimeStr = refundPayOrderObj.getString(WxPayConstant.NEU_REFUND_ORDER_OUT_TIME);
            Long posPayOrderId, posRefundOrderId;
            Integer neuRefundPayOrderType, neuRefundAmount, neuRefundPayOrderStatus, neuRefundType;
            try {
                neuRefundPayOrderType = Integer.valueOf(neuRefundOrderTypeStr);
                posPayOrderId = Long.valueOf(neuPayOrderIdStr);
                posRefundOrderId = Long.valueOf(neuRefundOrderIdStr);
                neuRefundAmount = Integer.valueOf(neuRefundOrderAmountStr);
                neuRefundPayOrderStatus = Integer.valueOf(neuRefundOrderStatusStr);
                neuRefundType = Integer.valueOf(neuRefundTypeStr);
            } catch (NumberFormatException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
            }
            Date posRefundOrderTime;
            try {
                posRefundOrderTime = neuDateFormat.parse(neuRefundOrderTimeStr);
            } catch (ParseException e) {
                logger.error(e.getMessage());
                throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), e.getMessage());
            }
            refundOrderFromPos(buUser, refundOrder, memId,
                    posPayOrderId, posRefundOrderId, posRefundOrderTime,
                    neuRefundPayOrderStatus, neuRefundAmount,neuRefundType,
                    neuRefundThirdNo);


        }
    }

    /**
     * 支付订单-会员折扣
     * @param merchantBUser
     * @param order
     * @param memId
     * @param posPayOrderId
     * @param neuPaymentAmount
     * @param neuPosPayOrderTime
     */
    public WxPayOrder payOrderFromMemDiscount(
            WxMerchantBUser merchantBUser,
            WxOrder order, Long memId,
            Long posPayOrderId, Integer neuPaymentAmount, Date neuPosPayOrderTime) {
        WxPayOrder payOrder = new WxPayOrder() {{
            Date curDate = new Date();
            final IdWorker idWorker = IdWorker.get();
            Long id = idWorker.nextId();
            setId(id);
            setTenantId(merchantBUser.getTenantId());
            setCUserId(memId);
            setOrderId(order.getId());
            setCreateTime(curDate);
            setUpdateTime(curDate);
            setPayTimeStart(curDate);
            setPayTimeEnd(curDate);
            setPayOrderNo(String.valueOf(id));
            setPayAmount(neuPaymentAmount);
            setPayVendor(EnumPayWay.PAY_WAY_POS_NEU.getCode());
            setPayOrderStatus(EnumPayStatus.PAY_STATUS_SUCCESS.getCode());
            setShare(EnumPayShare.NO.getCode());
            setPosPayOrderId(posPayOrderId);
            setPosPayOrderTime(neuPosPayOrderTime.getTime());
            /// TODO
            /// setPosPaymentType(EnumNeuPosPamentType.MEM_DISCOUNT.getCode());
            setPosPayDes("POS会员折扣支付");
        }};
        int sqlRow = payOrderMapper.insert(payOrder);
        if (sqlRow != 1) {
            logger.error("pay order insert error: " + payOrder.toString());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }
        return payOrder;
    }

    /**
     * 支付订单-优惠券
     * @param merchantBUser
     * @param order
     * @param memId
     * @param posPayOrderId
     * @param neuPaymentAmount
     * @param neuPosPayOrderTime
     */
    public WxPayOrder payOrderFromCouponVerify(
            PromotionCalc scoreCreditCalc,
            WxMerchantBUser merchantBUser,  WxOrder order,
            Long memId, Integer sceneType,
            Long posPayOrderId, Integer neuPaymentAmount, Date neuPosPayOrderTime, String posPayOrderNo) {
        // 1. insert into PayOrder
        WxPayOrder payOrder = new WxPayOrder() {{
            Date curDate = new Date();
            final IdWorker idWorker = IdWorker.get();
            Long id = idWorker.nextId();
            setId(id);
            setTenantId(merchantBUser.getTenantId());
            setCUserId(memId);
            setBUserId(merchantBUser.getId());
            setOrderId(order.getId());
            setCreateTime(curDate);
            setUpdateTime(curDate);
            setPayTimeStart(curDate);
            setPayTimeEnd(curDate);
            setPayOrderNo(String.valueOf(id));
            setPayAmount(neuPaymentAmount);
            setPayVendor(EnumPayWay.PAY_WAY_POS_NEU.getCode());
            setPayOrderStatus(EnumPayStatus.PAY_STATUS_SUCCESS.getCode());
            setShare(EnumPayShare.NO.getCode());
            setPayFrom(EnumPayFrom.POS.getCode());
            setPosPayOrderId(posPayOrderId);
            setPosPayOrderTime(neuPosPayOrderTime.getTime());
            setPosPaymentType(EnumNeuPosPamentType.MEM_COUPON.getCode());
            setPosPayDes("POS优惠券支付");
            setPosPayOrderNo(posPayOrderNo);
        }};
        int sqlRow = payOrderMapper.insert(payOrder);
        if (sqlRow != 1) {
            logger.error("pay order insert error: " + payOrder.toString());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }
        Long couponOrderId = Long.valueOf(posPayOrderNo);
        // 2. 预核销->核销
        if (sceneType.equals(EnumPosSceneType.PAY.getCode())) {
            WxCouponOrder updateCO = new WxCouponOrder();
            updateCO.setId(couponOrderId);
            updateCO.setCouponOrderStatus(EnumCouponOrderStatus.COUPON_ORDER_USED.getCode());
            couponOrderMapper.updateById(updateCO);
        }
        WxCouponOrderCVo couponOrderCVo = couponOrderService.detailCUserVo(posPayOrderNo);
        if (couponOrderCVo != null) {
            // 3. 成长值
            try {
                WxOrder scoreOrder = orderMapper.selectById(couponOrderCVo.getOrderId());
                int point = scoreRulesService.addScore2(EnumScoreType.CONSUMPTION, scoreOrder, couponOrderCVo.getBusinessId());
                scoreCreditCalc.setScore(scoreCreditCalc.getScore() + point);
            } catch (Exception e) {
                logger.error("交易核销成长值异常:" + e.getMessage());
            }

            WxCoupon wxCoupon = couponMapper.selectById(couponOrderCVo.getCouponId());
            if (wxCoupon == null) {
                logger.error("券: coupon-" + wxCoupon.getId() + "不存在" + "couponOrder-" + couponOrderCVo.getId());
                throw new MallinkException(ErrorCode.COUPON_ORDER_IS_NULL);
            }

            // 4. 积分
            try {
                int credit = posAddCredit(couponOrderCVo, wxCoupon, merchantBUser.getMerchantId(), merchantBUser.getId());
                scoreCreditCalc.setCredit(scoreCreditCalc.getCredit() + credit);
            } catch (Exception e) {
                logger.error("交易核销积分异常:" + e.getMessage());
            }
            // 3. 核销后处理
            couponOrderService.sendInsideCouponVerifyMsg(merchantBUser.getTenantId(), couponOrderId, merchantBUser.getId());
        }
        return payOrder;
    }

    /**
     * 支付订单-预付卡
     * @param merchant
     * @param buUser
     * @param order
     * @param memId
     * @param posPayOrderId
     * @param neuPaymentAmount
     * @param neuPosPayOrderTime
     * @param posPayOrderNo
     */
    public WxPayOrder payOrderFromCard(
            PromotionCalc scoreCreditCalc,
            WxMerchant merchant, WxMerchantBUser buUser,
            WxOrder order, Long memId,
            Long posPayOrderId, Integer neuPaymentAmount, Date neuPosPayOrderTime, String posPayOrderNo) {
        // 1. insert into PayOrder
        WxPayOrder payOrder = new WxPayOrder() {{
            Date curDate = new Date();
            final IdWorker idWorker = IdWorker.get();
            Long id = idWorker.nextId();
            setId(id);
            setTenantId(buUser.getTenantId());
            setCUserId(memId);
            setBUserId(buUser.getId());
            setOrderId(order.getId());
            setCreateTime(curDate);
            setUpdateTime(curDate);
            setPayTimeStart(curDate);
            setPayTimeEnd(curDate);
            setPayOrderNo(String.valueOf(id));
            setPayAmount(neuPaymentAmount);
            setPayVendor(EnumPayWay.PAY_WAY_POS_NEU.getCode());
            setPayOrderStatus(EnumPayStatus.PAY_STATUS_SUCCESS.getCode());
            setShare(EnumPayShare.NO.getCode());
            setPayFrom(EnumPayFrom.POS.getCode());
            setPosPayOrderId(posPayOrderId);
            setPosPayOrderTime(neuPosPayOrderTime.getTime());
            setPosPaymentType(EnumNeuPosPamentType.MEM_CARD.getCode());
            setPosPayDes("POS富茂卡支付");
            setPosPayOrderNo(posPayOrderNo);

        }};
        int sqlRow = payOrderMapper.insert(payOrder);
        if (sqlRow != 1) {
            logger.error("pay order insert error: " + payOrder.toString());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }
        // 2. 卡支付
        Long cardSpendId;
        try {
            cardSpendId = Long.valueOf(posPayOrderNo);
        } catch (NumberFormatException e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR);
        }
        WxCardSpend cardSpend = cardSpendService.getById(cardSpendId);
        cardSpend.setOrderId(order.getId());
        cardSpendService.cardSpendForPosPay(scoreCreditCalc, cardSpend, merchant, buUser);
        return payOrder;
    }

    /**
     * 支付订单POS
     * @param merchant
     * @param buUser
     * @param order
     * @param memId
     * @param neuPayOrderType
     * @param neuPayOrderId
     * @param neuPaymentAmount
     * @param neuPosPayOrderTime
     * @param neuPayThirdNo
     * @return
     */
    public WxPayOrder payOrderFromPos(
            PromotionCalc scoreCreditCalc,
            WxMerchant merchant, WxMerchantBUser buUser, WxOrder order, Long memId,
            Integer neuPayOrderType, Long neuPayOrderId, Integer neuPaymentAmount, Date neuPosPayOrderTime, String neuPayThirdNo) {
        // 1. insert into PayOrder
        WxPayOrder payOrder = new WxPayOrder() {{
            Date curDate = new Date();
            final IdWorker idWorker = IdWorker.get();
            Long id = idWorker.nextId();
            setId(id);
            setTenantId(buUser.getTenantId());
            setCUserId(memId);
            setOrderId(order.getId());
            setCreateTime(curDate);
            setUpdateTime(curDate);
            setPayTimeStart(curDate);
            setPayTimeEnd(curDate);
            setPayOrderNo(String.valueOf(id));
            setPayAmount(neuPaymentAmount);
            setPayVendor(EnumPayWay.PAY_WAY_POS_NEU.getCode());
            setPayOrderStatus(EnumPayStatus.PAY_STATUS_SUCCESS.getCode());
            setShare(EnumPayShare.NO.getCode());
            setPayFrom(EnumPayFrom.POS.getCode());
            setPosPayOrderId(neuPayOrderId);
            setPosPayOrderTime(neuPosPayOrderTime.getTime());
            setPosPaymentType(neuPayOrderType);
            setPosPayDes("POS支付");
            setPosPayOrderNo(neuPayThirdNo);

        }};
        int sqlRow = payOrderMapper.insert(payOrder);
        if (sqlRow != 1) {
            logger.error("pay order insert error: " + payOrder.toString());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }

        // 2. 积分, 成长值
        // 3. 成长值
        try {
            WxOrder scoreOrder = orderMapper.selectById(order.getId());
            scoreOrder.setPayment(neuPaymentAmount);
            int point = scoreRulesService.addScore2(EnumScoreType.CONSUMPTION, scoreOrder, merchant.getBusinessId());
            scoreCreditCalc.setScore(scoreCreditCalc.getScore() + point);
        } catch (Exception e) {
            logger.error("交易核销成长值异常:" + e.getMessage());
        }

        // 4. 积分
        try {
            int credit = posAddCreditForPay(merchant, buUser.getId(), memId, neuPaymentAmount);
            scoreCreditCalc.setCredit(scoreCreditCalc.getCredit() + credit);
        } catch (Exception e) {
            logger.error("交易核销积分异常:" + e.getMessage());
        }

        return payOrder;
    }

    /**
     * 退款订单POS
     * @param buUser
     * @param refundOrder
     * @param memId
     * @return
     */
    public WxRefundOrder refundOrderFromPos(
            WxMerchantBUser buUser, WxOrder refundOrder, Long memId,
            Long posPayOrderId, Long posRefundOrderId, Date posRefundOrderTime,
            Integer posRefundOrderStatus, Integer posRefundAmount, Integer posRefundType,
            String neuRefundThirdNo) {
        // 1. pay order
        // 查找支付订单
        WxPayOrder payOrderQ = new WxPayOrder() {{
            setTenantId(refundOrder.getTenantId());
            setCUserId(refundOrder.getCUserId());
            setOrderId(refundOrder.getId());
            setPayOrderStatus(EnumPayStatus.PAY_STATUS_SUCCESS.getCode());
            setPosPayOrderId(posPayOrderId);
        }};

        try {
            payOrderQ = payOrderMapper.selectOne(new QueryWrapper<>(payOrderQ));
        } catch (Exception e) {
            throw new MallinkException(ErrorCode.PAY_ORDER_NOT_FOUND);
        }
        if (payOrderQ == null) {
            logger.error("支付订单不存在(payOrder不存在)");
            throw new MallinkException(ErrorCode.PAY_ORDER_NOT_FOUND);
        }
        // 2. insert into refund order
        WxRefundOrder refundPayOrder = new WxRefundOrder() {{
            Date curDate = new Date();
            final IdWorker idWorker = IdWorker.get();
            Long id = idWorker.nextId();
            setId(id);
            setRefundId(String.valueOf(id));
            setTenantId(buUser.getTenantId());
            setCUserId(memId);
            setOrderId(refundOrder.getId());
            setCreateTime(curDate);
            setUpdateTime(curDate);
            setRefundTimeStart(curDate);
            setRefundTimeEnd(curDate);
            setTotalFee(posRefundAmount);
            setRefundFee(posRefundAmount);
            setRefundFrom(EnumPayFrom.POS.getCode());
            setRefundOrderStatus(EnumRefundStatus.REFUND_SUCCESS.getCode());
            setPosPayOrderId(posPayOrderId);
            setPosRefundOrderId(posRefundOrderId);
            setPosRefundOrderTime(posRefundOrderTime.getTime());
            setPosRefundOrderStatus(posRefundOrderStatus);
            setPosRefundType(posRefundType);
            setPosRefundDes("POS退款");
            setPosRefundOrderNo(neuRefundThirdNo);

        }};
        refundPayOrder.setPayOrderNo(String.valueOf(payOrderQ.getId()));

        int sqlRow = refundOrderMapper.insert(refundPayOrder);
        if (sqlRow != 1) {
            logger.error("refund order insert error: " + refundPayOrder.toString());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }

        return refundPayOrder;
    }

    /**
     * 基于NEUPOS订单信息创建订单
     * @param merchantBUser
     * @param memId
     * @param posOrderId
     * @param posAmount
     * @param posOrderTime
     * @return
     */
    public WxOrder createOrderByPos(
            final WxMerchantBUser merchantBUser,
            final Long memId,
            final Long posOrderId, final Integer posAmount, final Date posOrderTime, final String orderCreateSn
    ) {
        boolean isExist = true;
        WxOrder orderQ = new WxOrder() {{
            setType(EnumOrderType.POSPAY.getCode());
            setCUserId(memId);
            setPaymentType(EnumPayType.PAY_PAYMENT.getCode());
            setPosOrderId(posOrderId);
        }};
        try {
            orderQ = orderMapper.selectOne(new QueryWrapper<>(orderQ));
        } catch (Exception e) {
            isExist = false;
        }
        if (orderQ != null) {
            logger.error(ErrorCode.POS_ORDER_HAD_SYNCED.getMessage());
            throw new MallinkException(ErrorCode.POS_ORDER_HAD_SYNCED);
        }

        WxOrder order = new WxOrder() {{
            Date curr = new Date();
            String bodyStr = "POS支付, 金额: " + posAmount;
            final IdWorker idWorker = IdWorker.get();
            Long orderId = idWorker.nextId();
            setId(orderId);
            setTenantId(merchantBUser.getTenantId());
            setOrderNumber(orderId);
            setType(EnumOrderType.POSPAY.getCode());
            setProductId(merchantBUser.getId());
            setCUserId(memId);
            setPaymentType(EnumPayType.PAY_PAYMENT.getCode());
            setPayment(posAmount);
            setOrderStatus(EnumOrderStatus.ORDER_STATUS_PAYMENT_SUCCESS.getCode());
            setDetail(bodyStr);
            setCreateDate(curr);
            setUpdateDate(curr);
            setPosOrderId(posOrderId);
            setPosOrderCreateTime(posOrderTime.getTime());
            setPosOrderCreateSn(orderCreateSn);
        }};

        try {
            orderMapper.insert(order);
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }
        return order;
    }

    /**
     * 基于NEUPOS退款订单信息创建退款订单
     * @param buUser
     * @param memId
     * @param posOrderId
     * @param orderAmount
     * @param posOrderTime
     * @return
     */
    public WxOrder createRefundByPos(
            final WxMerchantBUser buUser,
            final Long memId,
            final Long posOrderId, final Integer orderAmount, final Date posOrderTime, final String orderCreateSn
    ) {
        // 1. 查找正向订单
        WxOrder orderQ = new WxOrder() {{
            setType(EnumOrderType.POSPAY.getCode());
            setCUserId(memId);
            setPaymentType(EnumPayType.PAY_PAYMENT.getCode());
            setPosOrderId(posOrderId);
        }};
        try {
            orderQ = orderMapper.selectOne(new QueryWrapper<>(orderQ));
        } catch (Exception e) {
            throw new MallinkException(ErrorCode.ORDER_IS_NOT_FIND);
        }
        if (orderQ == null) {
            logger.error(ErrorCode.POS_ORDER_NOT_FOUND.getMessage());
            throw new MallinkException(ErrorCode.POS_ORDER_NOT_FOUND);
        }

        // 2. 查找是否有退款订单
        WxOrder refundQ = new WxOrder() {{
            setType(EnumOrderType.POSPAY.getCode());
            setCUserId(memId);
            setPosOrderId(posOrderId);
        }};
        refundQ.setOrderNumber(orderQ.getOrderNumber());
        try {
            refundQ = orderMapper.selectOne(new QueryWrapper<>(refundQ));
        } catch (Exception e) {
        }
        if (refundQ != null) {
            logger.error(ErrorCode.POS_ORDER_HAD_SYNCED.getMessage());
            throw new MallinkException(ErrorCode.POS_ORDER_HAD_SYNCED);
        }

        // 3. 创建退款订单
        WxOrder order = new WxOrder() {{
            Date curr = new Date();
            String bodyStr = "POS退款, 金额: " + orderAmount;
            final IdWorker idWorker = IdWorker.get();
            Long orderId = idWorker.nextId();
            setId(orderId);
            setTenantId(buUser.getTenantId());
            setType(EnumOrderType.POSPAY.getCode());
            setProductId(buUser.getId());
            setCUserId(memId);
            setPaymentType(EnumPayType.PAY_POS_REFUND.getCode());
            setPayment(orderAmount);
            setOrderStatus(EnumOrderStatus.ORDER_STATUS_REFUND_SUCCESS.getCode());
            setDetail(bodyStr);
            setCreateDate(curr);
            setUpdateDate(curr);
            setPosOrderId(posOrderId);
            setPosOrderCreateTime(posOrderTime.getTime());
            setPosOrderCreateSn(orderCreateSn);
        }};
        order.setOrderNumber(orderQ.getOrderNumber());

        try {
            orderMapper.insert(order);
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new MallinkException(ErrorCode.DB_FAIL);
        }
        return order;
    }


    /**
     * 检查及获取商户信息
     * @param merchantId
     * @return
     */
    private WxMerchant checkAndGetMerchant(Long merchantId) {
        WxMerchant merchant = merchantService.getById(merchantId);
        if (merchant == null) {
            logger.error(ErrorCode.MERCHANT_INFO_NOT_FOUND.getMessage() + ": " + merchantId);
            throw new MallinkException(ErrorCode.MERCHANT_INFO_NOT_FOUND);
        }
        if (merchant.getIsDel().equals(EnumDelStatus.DEL.getCode()) ||
                merchant.getStatus().equals(EnumMerchantStatus.NOT_VALID.getCode())) {
            logger.error(ErrorCode.MERCHANT_INFO_NOT_VALID.getMessage() + ": " + merchantId);
            throw new MallinkException(ErrorCode.MERCHANT_INFO_NOT_VALID);
        }
        return merchant;
    }

    /**
     * 检查及获取商户用户
     * @param buUserId
     * @param merchantId
     */
    private WxMerchantBUser checkAndGetMerchantUser(Long buUserId, Long merchantId) {
        WxMerchantBUser buUser = merchantBUserService.getById(buUserId);
        if (buUser == null) {
            logger.error(ErrorCode.USER_IS_EMPTY.getMessage() + ": " + buUserId);
            throw new MallinkException(ErrorCode.USER_IS_EMPTY);
        }
        if (!buUser.getMerchantId().equals(merchantId)) {
            logger.error(ErrorCode.VERIFY_BUSER_MERCHANT_NOT_BELONG.getMessage() + ": " + buUserId);
            throw new MallinkException(ErrorCode.VERIFY_BUSER_MERCHANT_NOT_BELONG);
        }
        return buUser;
    }

    /**
     * 券是否在选择列表中
     * @param selCoArr
     * @param couponOrderId
     * @return
     */
    private boolean isSelCoupon(JSONArray selCoArr, Long couponOrderId) {
        boolean selCoupon = false;
        for (int j=0;j<selCoArr.size();j++) {
            String selCouponOrderStr = selCoArr.getString(j);
            if (couponOrderId.toString().equals(selCouponOrderStr)) {
                selCoupon = true;
                break;
            }
        }
        return selCoupon;
    }

    /**
     * 输入参数错误
     * @param paramStr
     * @throws MallinkException
     */
    private void errParam(String paramStr) throws MallinkException {
        String errMessage = String.format("request params[%s] error.", paramStr);
        logger.error(errMessage);
        throw new MallinkException(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(), errMessage);
    }

    /**
     * 输入参数错误
     * @param param1Str
     * @param param2Str
     * @throws MallinkException
     */
    private void errParam2(String param1Str, String param2Str) throws MallinkException {
        String errMessage = String.format("please give one value for %s or %s", param1Str, param2Str);
        logger.error(errMessage);
        throw new MallinkException(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(), errMessage);
    }

    /**
     * 输入参数错误
     * @param paramStr
     * @throws MallinkException
     */
    private void errParamCast(String paramStr, String valStr) throws MallinkException {
        String errMessage = String.format("request params[%s] error: %s", paramStr, valStr);
        logger.error(errMessage);
        throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), errMessage);
    }
}
