package com.unionpay.uas.demo.acp.bke;

import com.unionpay.uas.sdk.DemoUtil;
import com.unionpay.uas.sdk.SDKConfig;
import com.unionpay.uas.sdk.SDKConstants;
import com.unionpay.uas.sdk.UasService;
import com.unionpay.uas.sdk.gm.GmSDKConfig;
import com.unionpay.uas.sdk.gm.GmUasService;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * 预授权短信    bizMethod：upntAcp.contract.preAuthSms.send
 * 声明：以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己需要，按照技术文档编写。该代码仅供参考，不提供编码，性能，规范性等方面的保障<br>
 */
public class Demo13ContractPreAuthSmsSend {

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

    public static void main(String[] args) {
        // 使用国密算法调用此方法
        GmDemo();

        // 使用国际算法调用此方法
        //RsaDemo();
    }

    public static void GmDemo() {

        // 初始化配置文件，读取证书
        GmSDKConfig.loadPropertiesFromSrc();

        Map<String, String> reqData = new TreeMap<>();

        // 组装HTTP请求报文头
        reqData.put(SDKConstants.param_bizMethod, "upntAcp.contract.preAuthSms.send");       // 交易类型 根据实际业务填写
        reqData.put(SDKConstants.param_version, "7.0.1");                // 版本号 根据实际业务填写
        reqData.put(SDKConstants.param_signMethod, "SM2");               // 签名方法
        reqData.put(SDKConstants.param_appId, "987654321011111");        // 发送方系统索引号 根据实际业务填写
        reqData.put(SDKConstants.param_appType, "02");                   // 01机构、02商户
        reqData.put(SDKConstants.param_reqId, DemoUtil.getReqId());

        // 组装HTTP请求报文体 根据实际业务填写
        Map<String, Object> bodyMap = new HashMap<>();
        bodyMap.put("accessType", "0"); // 0：普通商户直连接入; 1：收单机构接入; 2：平台类商户接入
        bodyMap.put("merId", "987654321011111"); //商户号，请修改为自己的商户号
        bodyMap.put("orderId", DemoUtil.getOrderId()); //商户订单号，8-32位数字字母，不能含“-”或“_”，可以自行定制规则，此处为演示方便默认取了个值
        bodyMap.put("txnTime", DemoUtil.getTxnTime()); //订单发送时间，格式为yyyyMMddHHmmss，取北京时间，此处为演示方便默认取了个值
        // 注意：
        String accessType = (String) bodyMap.get("accessType");
        // 仅当accessType=1时，接入类型为收单机构时上送acqInsCode、merInfo
        if ("1".equals(accessType)) {
            bodyMap.put("acqInsCode", ""); // 收单机构号,根据实际接入方填写
            bodyMap.put("merInfo", buildMerInfo());    // 商户信息域,根据实际接入方填写
        } else if ("2".equals(accessType)) {
            // 仅当accessType=2时，接入类型为平台类商户接入时上送subMerInfo
            bodyMap.put("subMerInfo", buildSubMerInfo());
        }

        //Token信息域	tokenInfo,必送：trId、token
        Map<String, Object> tokenInfoMap = new HashMap<>();
        tokenInfoMap.put("trId", ""); //标记请求者ID
        tokenInfoMap.put("tokenType", ""); //token类型
        tokenInfoMap.put("token", "");
        tokenInfoMap.put("tokenEnd", ""); //标记失效时间格式为yyyyMMddHHmmss
        bodyMap.put("tokenInfo", tokenInfoMap);

        bodyMap.put("txnAmt", ""); //交易金额
        bodyMap.put("currencyCode", ""); //交易币种
        bodyMap.put("reqReserved", "这是请求方保留域"); //请求方保留域
        bodyMap.put("payAppId", ""); //当支付工具接入时，必送

        reqData.put(SDKConstants.param_body, JSONObject.wrap(bodyMap).toString());

        Map<String, String> reqMap = GmUasService.sign(reqData);

        // 发送请求报文并接受同步应答
        // 这里调用签名方法sign之后 调用post之前不能对map中的键值对做任何修改 如果修改会导致验签不通过
        Map<String, Object> rspMap = UasService.post(reqMap, GmSDKConfig.getConfig().getTransUrl());

        if (rspMap == null || rspMap.isEmpty()) {
            logger.info("POST请求服务端失败");
            return;
        }

        Map<String, String> respHeader = (Map<String, String>) rspMap.get(SDKConstants.param_header);
        String respBodyStr = (String) rspMap.get(SDKConstants.param_body);

        // 组装验签报文
        // 注：银联网关返回的应答头字段是驼峰式的 但在某些特殊场景(如HTTP 2.0)下头字段会转为小写; 此处为不区分大小写 在解析应答头时将所有字段名统一转换为小写;
        // 注：调用请求方在使用其他HTTP工具库时需要自行注意兼容头字段大小写问题
        Map<String, String> verifyMap = new HashMap<>();
        verifyMap.put(SDKConstants.param_signId, respHeader.get("signid"));
        verifyMap.put(SDKConstants.param_sign, respHeader.get("sign"));
        verifyMap.put(SDKConstants.param_bizMethod, respHeader.get("bizmethod"));
        verifyMap.put(SDKConstants.param_version, respHeader.get("version"));
        verifyMap.put(SDKConstants.param_signMethod, respHeader.get("signmethod"));
        verifyMap.put(SDKConstants.param_appId, respHeader.get("appid"));
        verifyMap.put(SDKConstants.param_reqId, respHeader.get("reqid"));
        verifyMap.put(SDKConstants.param_signPubKeyCert, respHeader.get("signpubkeycert"));
        verifyMap.put(SDKConstants.param_body, respBodyStr);

        if (!GmUasService.validate(verifyMap)) {
            logger.info("验证银联签名失败\n");
            return;
        } else {
            logger.info("验证银联签名成功\n");
        }

        Map<String, Object> rspBody;
        try {
            logger.info("从银联获得HTTP应答报文为：" + respBodyStr + "\n");
            rspBody = DemoUtil.unwarpJson(new JSONObject(respBodyStr));
            if (rspBody.containsKey("encryptData")) {
                Map<String, Object> encryptData = (Map<String, Object>) rspBody.get(SDKConstants.param_encryptData);
                String decKey = (String) encryptData.get(SDKConstants.param_key);
                String decData = (String) encryptData.get(SDKConstants.param_data);
                String certId = (String) encryptData.get(SDKConstants.param_certId);
                try {
                    String decodeStr = GmUasService.gmDec(decData, decKey, certId);
                    logger.info("解银联加密后的敏感信息的报文：" + decodeStr);
                } catch (Exception e) {
                    logger.error(e);
                }
            }

            if (rspBody.get("respInfo") != null) {
                String respInfo = (String) rspBody.get("respInfo");
                logger.info("应答信息域为：" + respInfo + "\n");
            }
        } catch (JSONException e) {
            logger.info("应答体解json失败。");
        }

    }

    public static void RsaDemo() {

        // 初始化配置文件，读取证书
        SDKConfig.loadPropertiesFromSrc();

        Map<String, String> reqData = new TreeMap<>();

        // 组装HTTP请求报文头
        reqData.put(SDKConstants.param_bizMethod, "upntAcp.contract.preAuthSms.send");       // 交易类型 根据实际业务填写
        reqData.put(SDKConstants.param_version, "7.0.1");                // 版本号 根据实际业务填写
        reqData.put(SDKConstants.param_signMethod, "RSA2");               // 签名方法
        reqData.put(SDKConstants.param_appId, "00010000");        // 发送方系统索引号 根据实际业务填写
        reqData.put(SDKConstants.param_appType, "02");                   // 01机构、02商户
        reqData.put(SDKConstants.param_reqId, DemoUtil.getReqId());      // 发送方流水号，可以自行定制规则

        // 组装HTTP请求报文体 根据实际业务填写
        Map<String, Object> bodyMap = new HashMap<>();
        bodyMap.put("accessType", "0"); // 0：普通商户直连接入; 1：收单机构接入; 2：平台类商户接入
        bodyMap.put("merId", "987654321011111"); //商户号，请修改为自己的商户号
        bodyMap.put("orderId", DemoUtil.getOrderId()); //商户订单号，8-32位数字字母，不能含“-”或“_”，可以自行定制规则，此处为演示方便默认取了个值
        bodyMap.put("txnTime", DemoUtil.getTxnTime()); //订单发送时间，格式为yyyyMMddHHmmss，取北京时间，此处为演示方便默认取了个值
        // 注意：
        String accessType = (String) bodyMap.get("accessType");
        // 仅当accessType=1时，接入类型为收单机构时上送acqInsCode、merInfo
        if ("1".equals(accessType)) {
            bodyMap.put("acqInsCode", ""); // 收单机构号,根据实际接入方填写
            bodyMap.put("merInfo", buildMerInfo());    // 商户信息域,根据实际接入方填写
        } else if ("2".equals(accessType)) {
            // 仅当accessType=2时，接入类型为平台类商户接入时上送subMerInfo
            bodyMap.put("subMerInfo", buildSubMerInfo());
        }

        //Token信息域	tokenInfo,必送：trId、token
        Map<String, Object> tokenInfoMap = new HashMap<>();
        tokenInfoMap.put("trId", ""); //标记请求者ID
        tokenInfoMap.put("tokenType", ""); //token类型
        tokenInfoMap.put("token", "");
        tokenInfoMap.put("tokenEnd", ""); //标记失效时间格式为yyyyMMddHHmmss
        bodyMap.put("tokenInfo", tokenInfoMap);

        bodyMap.put("txnAmt", ""); //交易金额
        bodyMap.put("currencyCode", ""); //交易币种
        bodyMap.put("reqReserved", "这是请求方保留域"); //请求方保留域
        bodyMap.put("payAppId", ""); //当支付工具接入时，必送

        reqData.put(SDKConstants.param_body, JSONObject.wrap(bodyMap).toString());

        Map<String, String> reqMap = UasService.sign(reqData);

        // 发送请求报文并接受同步应答
        // 这里调用签名方法sign之后 调用post之前不能对map中的键值对做任何修改 如果修改会导致验签不通过
        Map<String, Object> rspMap = UasService.post(reqMap, SDKConfig.getConfig().getTransUrl());

        if (rspMap == null || rspMap.isEmpty()) {
            logger.info("POST请求服务端失败");
            return;
        }

        Map<String, String> respHeader = (Map<String, String>) rspMap.get(SDKConstants.param_header);
        String respBodyStr = (String) rspMap.get(SDKConstants.param_body);

        // 组装验签报文
        // 注：银联网关返回的应答头字段是驼峰式的 但在某些特殊场景(如HTTP 2.0)下头字段会转为小写; 此处为不区分大小写 在解析应答头时将所有字段名统一转换为小写;
        // 注：调用请求方在使用其他HTTP工具库时需要自行注意兼容头字段大小写问题
        Map<String, String> verifyMap = new HashMap<>();
        verifyMap.put(SDKConstants.param_signId, respHeader.get("signid"));
        verifyMap.put(SDKConstants.param_sign, respHeader.get("sign"));
        verifyMap.put(SDKConstants.param_bizMethod, respHeader.get("bizmethod"));
        verifyMap.put(SDKConstants.param_version, respHeader.get("version"));
        verifyMap.put(SDKConstants.param_signMethod, respHeader.get("signmethod"));
        verifyMap.put(SDKConstants.param_appId, respHeader.get("appid"));
        verifyMap.put(SDKConstants.param_reqId, respHeader.get("reqid"));
        verifyMap.put(SDKConstants.param_signPubKeyCert, respHeader.get("signpubkeycert"));
        verifyMap.put(SDKConstants.param_body, respBodyStr);

        if (!UasService.validate(verifyMap)) {
            logger.info("验证银联签名失败\n");
            return;
        } else {
            logger.info("验证银联签名成功\n");
        }

        Map<String, Object> rspBody;
        try {
            logger.info("从银联获得HTTP应答报文为：" + respBodyStr + "\n");
            rspBody = DemoUtil.unwarpJson(new JSONObject(respBodyStr));
            if (rspBody.containsKey("encryptData")) {
                Map<String, Object> encryptData = (Map<String, Object>) rspBody.get(SDKConstants.param_encryptData);
                String decKey = (String) encryptData.get(SDKConstants.param_key);
                String decData = (String) encryptData.get(SDKConstants.param_data);
                String certId = (String) encryptData.get(SDKConstants.param_certId);
                try {
                    String decodeStr = UasService.rsaDec(decData, decKey, certId);
                    logger.info("解银联加密后的敏感信息的报文：" + decodeStr);
                } catch (Exception e) {
                    logger.error(e);
                }
            }

            if (rspBody.get("respInfo") != null) {
                String respInfo = (String) rspBody.get("respInfo");
                logger.info("应答信息域为：" + respInfo + "\n");
            }
        } catch (JSONException e) {
            logger.info("应答体解json失败。");
        }
    }

    private static Map buildMerInfo() {
        Map<String, Object> merInfo = new HashMap<String, Object>(); //接入类型为收单机构接入时需上送
        merInfo.put("merCatCode", ""); //接入类型为收单机构接入时需上送，填写MCC码
        merInfo.put("merName", ""); //接入类型为收单机构接入时需上送，不支持换行符等不可见字符
        merInfo.put("merAbbr", ""); //接入类型为收单机构接入时需上送，最长8位，不支持换行符等不可见字符
        return merInfo;
    }

    private static Map buildSubMerInfo() {
        Map<String, Object> subMerInfo = new HashMap<String, Object>(); //接入类型为平台类接入时需上送
        subMerInfo.put("subMerId", ""); //二级商户代码
        subMerInfo.put("subMerName", ""); //二级商户名称
        subMerInfo.put("subMerAbbr", ""); //二级商户简称
        return subMerInfo;
    }
}
