package com.iformall.utils.car;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.iformall.common.ErrorCode;
import com.iformall.exception.MallinkException;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Title: OpenapiCaller
 * @Package: cn.etcp.openplatform.service
 * @Description: ETCP开放平台接口调用示例
 * @author: zhiyong.fan
 * @date: 2018/3/8 17:41
 * @version: V1.0
 */
public class ETCPUtil {

    public static final String ETCP_PARK_ID = "parkId";
    public static final String ETCP_CAR_NUMBER = "plateNumber";
    public static final String ETCP_SYN_ID = "synId";
    public static final String ETCP_PARK_NAME = "parkName";
    public static final String ETCP_USER_TYPE = "userType";
    public static final String ETCP_ENTRANCE_TIME = "entranceTime";
    public static final String ETCP_FIX_PARKING_ID = "fixParkingId";
    public static final String ETCP_REMAINING_DAYS = "remainingDays";
    public static final String ETCP_EXIT_TIME = "exitTime";
    public static final String ETCP_STAYED_TIME = "stayedTime";
    public static final String ETCP_ORDER_ID = "orderId";
    public static final String ETCP_PAID_SERVICE_FEE = "paidServiceFee";
    public static final String ETCP_TIME = "time";
    public static final String ETCP_FEE = "fee";

    private static final Logger logger = LoggerFactory.getLogger(ETCPUtil.class);

    /**
     *  域名地址
     */
    private static final String domain            = "http://mapi.test.etcp.cn";

    //
    private static final String appId             = "FMLK";

    private static final int payType             = 5;

    /**
     *  商户号 测试时请换成ETCP开放平台为商户平台分配的商户号
     */
    private static final String merchantNo        = "C7AEAF80BA8C44ADB42F3DB3CBC7D18A";

    /**
     *  商户密钥 测试时请换成ETCP开放平台为商户平台分配的商户密钥
     */
    private static final String merchantKey       = "C292FFC7DCFB46AFB02792CD43F6DCC7";

    /**
     *  接口服务版本号
     */
    private static final String version           = "1.0.0";

    /**
     *  6.1. 联合登录
     */
    private static final String userSigninUrl     = "/merchant/open/{version}/openapi/usersigin";

    /**
     *  7.1. 车牌绑定
     */
    private static final String bindCarUrl        = "/merchant/open/{version}/openapi/bindcar";

    /**
     *  7.2. 车牌解绑
     */
    private static final String unbindCarUrl      = "/merchant/open/{version}/openapi/unbindcar";

    /**
     *  7.3. 车牌认证
     */
    private static final String carAuthUrl        = "/merchant/open/{version}/openapi/carauth";

    /**
     *  7.4. 车牌找回
     */
    private static final String carRetrieveUrl    = "/merchant/open/{version}/openapi/carretrieve";

    /**
     *  7.5. 已绑车辆信息查询
     */
    private static final String carNumUrl         = "/merchant/open/{version}/openapi/carnum";

    /**
     *  8.1. 停车费查询
     */
    private static final String orderUnpayUrl     = "/merchant/open/{version}/openapi/orderunpay";

    /**
     *  8.2. 优惠券查询
     */
    private static final String couponListUrl     = "/merchant/open/{version}/openapi/couponlist";

    /**
     *  8.3. 历史停车查询
     */
    private static final String orderHistoryUrl   = "/merchant/open/{version}/openapi/orderhistory";

    /**
     *  9.1. 主动支付(ETCP 收款)
     */
    private static final String orderpayUrl       = "/merchant/open/{version}/openapi/orderpay";

    /**
     *  9.2. 主动支付(商户平台收款)
     */
    private static final String orderPaidUrl      = "/merchant/open/{version}/openapi/orderpaid";

    /**
     *  10.1. 签约地址获取
     */
    private static final String signUrl           = "/merchant/open/{version}/withhold/sign";

    /**
     *  10.2. 签约状态查询
     */
    private static final String statusUrl         = "/merchant/open/{version}/withhold/status";

    /**
     *  11.1. 车场信息查询
     */
    private static final String parkingInfoUrl    = "/merchant/open/{version}/openapi/surroundingparking";

    /**
     *  11.2. 车场状态查询
     */
    private static final String parkingStatusUrl  = "/merchant/open/{version}/openapi/parkingstatus";

    /**
     *  12.1. 坏账查询
     */
    private static final String getDebtUrl        = "/merchant/open/{version}/openapi/getdebt";

    /**
     *  12.2. 坏账清缴
     */
    private static final String repayDebtUrl      = "/merchant/open/{version}/openapi/repaydebt";

    /**
     *  13.1. 商家优免券模板查询
     */
    private static final String bCouponListUrl    = "/merchant/open/{version}/openapi/b/coupon/list";

    /**
     *  13.2. 商家优免券发放
     */
    private static final String bCouponRecordUrl  = "/merchant/open/{version}/openapi/b/coupon/record";

    /**
     *  14.1. 车辆进出场模拟接口
     */
    private static final String bCarSimulationUrl  = "/merchant/open/{version}/openapi/car/simulation";

    private final OkHttpClient client            = new OkHttpClient();

    private DateTimeFormatter   dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    /** 错误码
     * 0     成功
     * 1     失败
     * 2     服务器异常
     * 3     时间戳超时
     * 4     签名校验非法
     * 5     没有找到商户密钥
     * 6     参数错误
     * 7     更新订单失败
     * 8     生产订单失败
     * 20100 操作失败
     * 20101 无效 token
     * 20102 缺少必要参数
     * 20106 参数错误
     * 20201 手机号错误
     * 20202 获取验证码失败
     * 20203 手机验证码错误
     * 20206 支付金额与应付金额不匹配
     * 20207 60s 内只能发送一次验证码
     * 20208 APP 未有该权限
     * 20209 优惠金额与应付金额不匹配
     * 20301 签约失败
     * 20302 签约成功
     * 20401 无效车架号
     * 20407 车辆信息不匹配
     * 20408 操作失败,请稍后重试
     * 20501 操作失败,请稍后重试
     * 20602 returnUrl 为空
     * 20603 未查询到费用
     * 20604 请先清偿坏账
     * 20605 synId 错误
     * 20606 待支付费用为 0,无需支付
     * 20607 支付链接失效
     * 20608 查费失败
     * 20701 appId 错误
     * 20801 未绑定该车辆
     * 20802 该车辆已被他人绑定
     * 20803 绑定数量超限
     * 20804 30 天内最多绑定 4 辆不同的车
     * 20805 在场车辆不可解绑
     * 20901 车辆不在场
     * 20902 车场不支持电子支付
     * 20903 没有车场查费权限
     * 20904 用户未绑定该查费车牌
     * 20905 查询费用异常
     * 20906 用户不存在
     * 20907 商户未配置查费车场
     * 20908 长租车,费用为 0
     * 20909 车牌错误
     * 21001 查询失败
     * 21002 请稍后再试
     * 21003 未查询到坏账
     * 21004 错误支付类型
     * 21101 签约号已存在
     */

    public static void main(String[] args) {
        ETCPUtil caller = new ETCPUtil();
        String ret = "";
        JSONObject objret = null;
        String etcpToken = "899e94d7-a3a5-46d4-87a3-288b18d0b189";
        String carNumber = "京D12345";
        String parkId = "Wj7YdvqyiYM=";
        String businessId = "IgWGUtpAX68=";
        String couponFreeId = "5627";

        //caller.carSimulation(domain, appId, merchantNo, merchantKey, version, carNumber);
        /* {"code":0,"message":"ok","data":{"isInPark":0}} */

        //caller.getBCouponList(domain, merchantNo, merchantKey, version, parkId, businessId);
        // {"code":0,"message":"查询成功","data":{"count":1,"couponPlatformModels":[{"id":5627,"parkId":"Wj7YdvqyiYM=","businessId":"IgWGUtpAX68=","businessName":"fmtest5678","name":"优免现金1元","category":"2","categoryValue":"1.00","amount":100,"status":"1","effectiveStart":"2018-08-20","effectiveEnd":"2018-08-25","couponType":"0","avaliavleNum":100}]}}
        //caller.bCouponRecord(domain, merchantNo, merchantKey, version, etcpToken, parkId, businessId, carNumber, couponFreeId);
        // 联合登录测试

        /*ret = caller.userSignin(domain, appId, merchantNo, merchantKey, version, "13910154397");

        objret = JSON.parseObject(ret);
        if (objret.getIntValue("code") != 0)
            return;

        etcpToken = objret.getJSONObject("data").getString("token");
        System.out.println(etcpToken);
        */

        // 绑定车牌查询
        ret = caller.carNum(domain, merchantNo, merchantKey, version, etcpToken);
        System.out.println(ret);
        objret = JSON.parseObject(ret);
        if (objret.getIntValue("code") != 0)
            return;
        JSONObject data = objret.getJSONObject("data");
        if (data.getIntValue("number") <= 0) {
            // 车牌绑定测试
            ret = caller.bindCar(domain, etcpToken, carNumber, null, merchantNo, merchantKey, version);
            objret = JSON.parseObject(ret);
            if (objret.getIntValue("code") != 0)
                return;
        }
        JSONArray carArry = data.getJSONArray("carList");
        carNumber = JSON.toJSONString(carArry.get(0));
        carNumber = carNumber.substring(1, carNumber.length() -1);
        System.out.println(carNumber);
        // 停车费查询
        ret = caller.orderUnpay(domain, appId, merchantNo, merchantKey, version, etcpToken, carNumber);
        System.out.println(ret);
        objret = JSON.parseObject(ret);
        if (objret.getIntValue("code") != 0)
            return;
        JSONArray payArr = objret.getJSONArray("data");
        JSONObject payObj = payArr.getJSONObject(0);
        String orderId = payObj.getString("orderId");
        // 微信h5支付
        ret = caller.orderPay(domain, merchantNo, merchantKey, version, etcpToken, orderId, "http://test.cn", null);
        System.out.println(ret);

        // 车牌解绑测试
        //caller.unbindCar(merchantNo, merchantKey, version, etcpToken, carNumber);


        //String orderId = "867C6F2F-4F3A-4767-890E-BA7F53F9C601";
        //caller.orderPay(etcpToken, 5, orderId, "test.cn", null);

        // 支付完成通知测试
        /*caller.orderPaid(etcpToken, "51C3E00A-4F87-43FB-A71B-C1287ED956CB",
            "201803120000001", BigDecimal.valueOf(8.85), "2018-03-12 11:58:31",
            "FSED9JvxgAU6XJUd8YRF5f7OuzyQ/lGJch0h8dVVkNaHUpPISCLBshnn0AIoEdIdi2aQMOrrcmdVz2Smy+UbQ8pXl95Mv50aPNnUgaDlxSDqHQ8iOD6FT5XV7ikozZ0800CJKDkvQrmCZS2O21G+xg==",
            "ETCP");*/
    }

    public static int getPayType() {
        return payType;
    }

    private boolean checkRespFailed(String resp) {
        if(resp.startsWith("<html>") || resp.startsWith("<!DOCTYPE html>")) {
            return true;
        }
        return false;
    }

    /**
     *  6.1. 联合登录
     *
     * @param appId appId(非空)
     * @param merchantNo merchantNo(非空)
     * @param merchantKey merchantKey(非空)
     * @param version version(非空)
     * @param mobilePhone 手机号(非空)
     * @return 联合登录响应信息
     * {
     *    "code": 0,
     *    "data": {
     *       "token": "0b59582d-ea6f-4bf1-a570-e803cc24ebe0"
     *    },
     *    "message": "ok"
     * }
     */
    public String userSignin(String baseUrl, String appId, String merchantNo, String merchantKey, String version,
                             String mobilePhone) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appId", appId);
        jsonObject.put("mobilePhone", mobilePhone);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + userSigninUrl.replaceAll("\\{version}", version), data, sign,
            timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  7.1. 车牌绑定
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param plateNumber 车牌号(非空)
     * @param plateColor 车牌颜色(可空)
     * @return 绑牌响应信息
     * {
     *    "code": 0,
     *    "message": "ok"
     * }
     */
    public String bindCar(String baseUrl, String merchantNo, String merchantKey, String version,
                          String etcpToken,
                          String plateNumber, String plateColor) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("plateNumber", plateNumber);
        if ("1".equals(plateColor) || "2".equals(plateColor) || "3".equals(plateColor)
            || "4".equals(plateColor)) {
            jsonObject.put("plateColor", plateColor);
        }

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + bindCarUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  7.2. 车牌解绑
     * 
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param plateNumber 车牌号(非空)
     * @return 车牌解绑响应信息
     * {
     *    "code": 0,
     *    "message": "ok"
     * }
     */
    public String unbindCar(String baseUrl, String merchantNo, String merchantKey, String version,
                            String etcpToken,
                            String plateNumber) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("plateNumber", plateNumber);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + unbindCarUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  7.3. 车牌认证
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param carFrameNum  车架号(非空)
     * @param plateNumber  车牌号(非空)
     * @param carEngineNum 发动机号(非空)
     * @return 车牌解绑响应信息
     * {
     *    "code": 0,
     *    "message": "ok"
     * }
     */
    public String carAuth(String baseUrl, String merchantNo, String merchantKey, String version,
                          String etcpToken,
                          String carFrameNum, String plateNumber, String carEngineNum
                          ) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("carFrameNum", carFrameNum);
        jsonObject.put("plateNumber", plateNumber);
        jsonObject.put("carEngineNum", carEngineNum);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + carAuthUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  7.4. 车牌找回
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param carFrameNum  车架号(非空)
     * @param plateNumber  车牌号(非空)
     * @param carEngineNum 发机号(非空)
     * @param plateColor   车牌颜色(1 蓝 2 黑 3 黄 4 白)
     * @return 车牌找回响应信息
     * {
     *    "code": 0,
     *    "message": "ok"
     * }
     */
    public String carRetrieve(String baseUrl, String merchantNo, String merchantKey, String version,
                              String etcpToken,
                              String carFrameNum, String plateNumber, String carEngineNum, String plateColor ) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("carFrameNum", carFrameNum);
        jsonObject.put("plateNumber", plateNumber);
        jsonObject.put("carEngineNum", carEngineNum);
        if (!StringUtils.isBlank(plateColor))
            jsonObject.put("plateColor", plateColor);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + carRetrieveUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  7.5. 已绑车辆信息查询
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @return 已绑车辆信息查询响应信息
     * {
     * "code": 0,
     * "msg": "ok",
     * "data": {
     *   "number": 3,
     *   "carList": [
     *      "晋 BMZ105",
     *      "云 C12345"
     *      ]
     *  }
     * }
     */
    public String carNum(String baseUrl, String merchantNo, String merchantKey, String version,
                         String etcpToken) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doGet(baseUrl + carNumUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  8.1. 停车费查询
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param plateNumber 车牌号(非空)
     * @param appId appId(可空)
     * @return 停车费查询响应信息
     */
    public String orderUnpay(String baseUrl,
                             String appId, String merchantNo, String merchantKey, String version,
                             String etcpToken,
                             String plateNumber) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("plateNumber", plateNumber);
        if (appId != null && appId.length() > 0) {
            jsonObject.put("appId", appId);
        }

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doGet(baseUrl + orderUnpayUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  8.2. 优惠券查询
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @param lastCode 上一分页代码(可空)
     * @param pageSize 1-200 之间的分页每页数量(非空)
     * @return 停车费查询响应信息
     */
    public String couponList(String baseUrl,
                             String merchantNo, String merchantKey, String version,
                             String etcpToken,
                             String lastCode, String pageSize) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        if (!StringUtils.isBlank(lastCode))
            jsonObject.put("lastCode", lastCode);
        jsonObject.put("pageSize", pageSize);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doGet(baseUrl + couponListUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  8.3.历史停车查询
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin}) (非空)
     * @return 停车费查询响应信息
     */
    public String orderHistory(String baseUrl,
                               String merchantNo, String merchantKey, String version,
                               String etcpToken) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doGet(baseUrl + orderHistoryUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  9.1. 主动支付(ETCP 收款)
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin})(非空)
     * @param orderId 停车记录ID ({@link ETCPUtil#orderUnpay})(非空)
     * @param returnUrl 外部支付流水号(商户平台生成)(非空)
     * @param couponCode 支付金额 ({@link ETCPUtil#orderUnpay})(非空)
     * @return
     */
    public String orderPay(String baseUrl,
                           String merchantNo, String merchantKey, String version,
                           String etcpToken, String orderId,
                           String returnUrl, String couponCode) throws MallinkException {
        // payType 支付方式(1 微信公众号内支付 2 支付宝 H5 支付 3 微信二维码 4 支付宝二维码 5 微信 H5)(非空)
        int payType = 1;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("payType", payType);
        jsonObject.put("synId", orderId);
        jsonObject.put("returnUrl", returnUrl);
        if (couponCode != null && couponCode.length() > 0) {
            jsonObject.put("couponCode", couponCode);
        }

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + orderpayUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  9.2. 主动支付(商户平台收款)
     *
     * @param etcpToken 商户平台调用联合接口获取的token({@link ETCPUtil#userSignin})(非空)
     * @param orderId 停车记录ID ({@link ETCPUtil#orderUnpay})(非空)
     * @param externalOrderId 外部支付流水号(商户平台生成)(非空)
     * @param payment 支付金额 ({@link ETCPUtil#orderUnpay})(非空)
     * @param payTime 支付时间, 格式:yyyy-MM-dd HH:mm:ss(非空)
     * @param verificationInfo 验证信息串 ({@link ETCPUtil#orderUnpay})(非空)
     * @param appId appId(可空)
     * @return
     */
    public String orderPaid(String baseUrl,
                            String appId, String merchantNo, String merchantKey, String version,
                            String etcpToken,
                            String orderId, String externalOrderId,
                            BigDecimal payment, String payTime, String verificationInfo) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("orderId", orderId);
        jsonObject.put("externalOrderId", externalOrderId);
        jsonObject.put("payment", payment);
        jsonObject.put("payTime", payTime);
        jsonObject.put("verificationInfo", verificationInfo);
        if (appId != null && appId.length() > 0) {
            jsonObject.put("appId", appId);
        }

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + orderPaidUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  11.1. 车场信息查询
     *
     * @param lat, lon, radius
     * @return 车场信息查询
     */
    public String parkingInfo(String baseUrl,
                                String merchantNo, String merchantKey, String version,
                                String lat, String lon, String radius) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("lat", lat);
        jsonObject.put("lon", lon);
        jsonObject.put("radius", radius);
        jsonObject.put("payType", "1"); // 1:电子支付

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + parkingInfoUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  11.2. 车场状态查询
     *
     * @param parkingId 车场ID(非空)
     * @return 车场状态查询响应信息
     */
    public String parkingStatus(String baseUrl,
                                   String merchantNo, String merchantKey, String version,
                                   String parkingId) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("parkingId", parkingId);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + parkingStatusUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)){
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  12.1. 坏账查询
     *
     * @param etcpToken
     * @return 车场状态查询响应信息
     */
    public String getDebt(String baseUrl,
                          String appId, String merchantNo, String merchantKey, String version,
                          String etcpToken, String carNumber) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("appId", appId);
        jsonObject.put("plateNumber", carNumber);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + parkingStatusUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  12.2. 坏账清缴
     *
     * @param etcpToken
     * @param
     * @param returnUrl
     * @return 车场状态查询响应信息
     */
    public String repayDebt(String baseUrl,
                            String merchantNo, String merchantKey, String version,
                          String etcpToken, String returnUrl) throws MallinkException {
        // payType(1:支付宝 2:微信H5)
        int payType = 2;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("payType", payType);
        jsonObject.put("returnUrl", returnUrl);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + parkingStatusUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  13.1. 商家优免券模板查询
     *
     * @return 商家优免券模板查询结果
     */
    public String getBCouponList(String baseUrl,
                                 String merchantNo, String merchantKey, String version,
                          String parkId, String businessId) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("parkId", parkId);
        jsonObject.put("businessId", businessId);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + bCouponListUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  13.2. 商家优免券发放
     *
     * @param etcpToken
     * @return 商家优免券发放结果
     */
    public String bCouponRecord(String baseUrl,
                                String merchantNo, String merchantKey, String version,
                                String etcpToken,
                                String parkId, String businessId, String carNumber, String couponFreeId) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("token", etcpToken);
        jsonObject.put("parkId", parkId);
        jsonObject.put("businessId", businessId);
        jsonObject.put("vehicleNo", carNumber);
        jsonObject.put("couponFreeId", couponFreeId);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + bCouponRecordUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    /**
     *  14.1. 车辆进出场模拟接口
     *
     * @param appId, plateNumber
     * @return 车场状态查询响应信息
     */
    public String carSimulation(String baseUrl,
                                String appId, String merchantNo, String merchantKey, String version,
                                String plateNumber) throws MallinkException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appId", appId);
        jsonObject.put("plateNumber", plateNumber);

        String data = jsonObject.toJSONString();
        String timeStamp = dateTimeFormatter.format(LocalDateTime.now());
        String sign = genSign(data, merchantKey, timeStamp);

        String respStr = doPost(baseUrl + bCarSimulationUrl.replaceAll("\\{version}", version),
                data, sign, timeStamp, merchantNo);
        if(respStr == null) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        if(checkRespFailed(respStr)) {
            throw new MallinkException(ErrorCode.ETCP_CMD_FAIL);
        }
        return respStr;
    }

    private String doPost(String url, String data, String sign, String timeStamp, String merchantNo) {
        logger.info("请求url地址:{}, 请求参数为:data={}, sign={}, time_stamp={}, merchant_no={}", url, data,
            sign, timeStamp, merchantNo);
        RequestBody body = new FormBody.Builder().add("data", data).add("sign", sign)
            .add("time_stamp", timeStamp).add("merchant_no", merchantNo).build();
        Request request = new Request.Builder().url(url).post(body).build();
        try (Response response = client.newCall(request).execute()) {
            String responseStr = response.body().string();
            logger.info("post接口响应信息为:{}", responseStr);
            return responseStr;
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    private String doGet(String sourceUrl, String data, String sign, String timeStamp, String merchantNo) {
        logger.info("请求url地址:{}, 请求参数为:data={}, sign={}, time_stamp={}, merchant_no={}", sourceUrl,
            data, sign, timeStamp, merchantNo);
        HttpUrl url = HttpUrl.parse(sourceUrl).newBuilder().addQueryParameter("data", data)
            .addQueryParameter("sign", sign).addQueryParameter("time_stamp", timeStamp)
            .addQueryParameter("merchant_no", merchantNo).build();
        Request request = new Request.Builder().url(url).build();
        try (Response response = client.newCall(request).execute()) {
            String responseStr = response.body().string();
            logger.info("get请求接口响应信息为:{}", responseStr);
            return responseStr;
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
        return null;
    }

    // 签名
    private String genSign(String param, String merchantKey, String time) {
        String preparedString = new StringBuilder(param).append(merchantKey).append(time).toString();
        return md5Encode(preparedString.replaceAll("[ \r\t\n]", ""));
    }

    private String md5Encode(String content) {
        StringBuilder sb = new StringBuilder();
        try {
            if (content == null || content.length() == 0) {
                return null;
            }
            MessageDigest digestInstance = MessageDigest.getInstance("MD5");
            digestInstance.update(content.getBytes("UTF-8"));
            byte[] md = digestInstance.digest();
            for (int i = 0; i < md.length; ++i) {
                int val = md[i] & 0xff;
                if (val < 16) {
                    sb.append("0");
                }
                sb.append(Integer.toHexString(val));
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }
        return sb.toString().toUpperCase();
    }

}
