package com.unionpay.acp.demo.acp7Contract.cardH5;

import com.unionpay.acp.demo.DemoUtil;
import com.unionpay.acp.sdk.Acp7Service;
import com.unionpay.acp.sdk.SDKConfig;
import com.unionpay.acp.sdk.SDKConstants;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;

/**
 * 预授权_卡片版（H5）
 * 声明：以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己需要，按照技术文档编写。该代码仅供参考，不提供编码，性能，规范性等方面的保障<br>
 * 更多问题请使用open网站查找，开放平台-帮助与支持-商户入网测试FAQ，https://open.unionpay.com/tjweb/support/faq/mchlist
 */
public class Demo10_12_ContractAuth_CardH5 {

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

    private String bizMethod;
    private String url;

    public Demo10_12_ContractAuth_CardH5(String bizMethod, String url) {
        this.bizMethod = bizMethod;
        this.url = url;
    }


    public static void main(String[] s) {
        Demo10_12_ContractAuth_CardH5 demo = new Demo10_12_ContractAuth_CardH5("acp.trade.contract.auth",
                SDKConfig.getConfig().getV7TransUrl());
        Map<String, String> reqMap;
        Map<String, String> rspMap;

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

        /***产品参数***/
        reqData.put("version", "7.0.1");                 //版本号
        reqData.put("signMethod", "RSA2");               //签名方法
        reqData.put("bizMethod", demo.bizMethod);        //交易类型

        /***商户接入参数***/
        reqData.put("appId", SDKConfig.getValueFromConfig("appId"));      //发送方系统索引号
        reqData.put("appType", SDKConfig.getValueFromConfig("appType"));                 //01机构、02商户
        reqData.put("reqId", DemoUtil.getReqId());    //发送方流水号，可以自行定制规则

        /***业务参数***/
        Map<String, Object> bodyMap = new HashMap<String, Object>();
        //TODO 以下参数请按真实情况填写
        bodyMap.put("accessType", SDKConfig.getValueFromConfig("accessType")); //1机构，0商户
        bodyMap.put("acqInsCode", SDKConfig.getValueFromConfig("acqInsCode"));//收单机构代码,机构接入必填
        bodyMap.put("merId", SDKConfig.getValueFromConfig("merId")); //商户号，请修改为自己的商户号
        bodyMap.put("orderId", DemoUtil.getOrderId()); //商户订单号，8-32位数字字母，不能含“-”或“_”，可以自行定制规则，此处为演示方便默认取了个值
        bodyMap.put("txnTime", DemoUtil.getTxnTime());//订单发送时间，格式为yyyyMMddHHmmss，取北京时间，此处为演示方便默认取了个值
        bodyMap.put("reqReserved", SDKConfig.getValueFromConfig("reqReserved"));//请求方保留域:支付成功后在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据


        if ("1".equals(SDKConfig.getValueFromConfig("accessType"))) {
            Map<String, Object> merInfo = new HashMap<>(); //接入类型为收单机构接入时需上送
            merInfo.put("merCatCode", SDKConfig.getValueFromConfig("merCatCode"));//商户类别,接入类型为收单机构接入时需上送，填写MCC码
            merInfo.put("merName", SDKConfig.getValueFromConfig("merName"));//商户名称
            merInfo.put("merAbbr", SDKConfig.getValueFromConfig("merAbbr")); //商户简称;
            bodyMap.put("merInfo", JSONObject.wrap(merInfo));//接入类型为收单机构接入时需上送
        }

        if ("2".equals(SDKConfig.getValueFromConfig("accessType"))) {
            Map<String, Object> subMerInfoMap = new HashMap<>(); //接入类型为收单机构接入时需上送
            subMerInfoMap.put("subMerId", SDKConfig.getValueFromConfig("subMerId"));//二级商户代码
            subMerInfoMap.put("subMerName", SDKConfig.getValueFromConfig("subMerName"));//二级商户名称
            subMerInfoMap.put("subMerAbbr", SDKConfig.getValueFromConfig("subMerAbbr")); //二级商户简称
            bodyMap.put("subMerInfo", JSONObject.wrap(subMerInfoMap));//接入类型为平台商户接入接入时需上送
        }

        Map<String, Object> accountVerifyInfo = new HashMap<String, Object>();
        accountVerifyInfo.put("certifTp", SDKConfig.getValueFromConfig("certifTp"));       //证件类型
        accountVerifyInfo.put("certifId", SDKConfig.getValueFromConfig("certifId"));       //证件号
        accountVerifyInfo.put("customerNm", SDKConfig.getValueFromConfig("customerNm"));       //姓名
        accountVerifyInfo.put("smsCode", SDKConfig.getValueFromConfig("smsCode"));       //短信验证码
        accountVerifyInfo.put("accNo", SDKConfig.getValueFromConfig("accNo"));       //卡号
        accountVerifyInfo.put("phoneNo", SDKConfig.getValueFromConfig("phoneNo"));       //手机号
        accountVerifyInfo.put("expired", SDKConfig.getValueFromConfig("expired"));       //有效期
        accountVerifyInfo.put("cvn2", SDKConfig.getValueFromConfig("cvn2"));       //cvn2
        accountVerifyInfo.put("accType", SDKConfig.getValueFromConfig("accType"));       //默认取值：01;具体取值范围：01：银行卡;05：token;08：苹果PAY设备卡号;09：安卓PAY设备卡号
        if ("1".equals(SDKConfig.getValueFromConfig("isEncrypt"))) {
            //入网配置加密时填写方式
            try {
                String key = DemoUtil.getRandomString(32);
                bodyMap.put("encryptData",
                        JSONObject.wrap(Acp7Service.rsaEnc(
                                Acp7Service.buildDataMap("accountVerifyInfo",
                                        Acp7Service.base64Encode(JSONObject.wrap(accountVerifyInfo).toString())),
                                key, SDKConstants.UTF_8_ENCODING)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            //入网配置不加密时填写方式
            bodyMap.put("accountVerifyInfo", Acp7Service.base64Encode(JSONObject.wrap(accountVerifyInfo).toString()));
        }

        Map<String, Object> tokenInfoMap = new HashMap<String, Object>();
        tokenInfoMap.put("trId", SDKConfig.getValueFromConfig("trId"));
        tokenInfoMap.put("tokenType", SDKConfig.getValueFromConfig("tokenType"));
        tokenInfoMap.put("token", SDKConfig.getValueFromConfig("token"));
        tokenInfoMap.put("tokenEnd", SDKConfig.getValueFromConfig("tokenEnd"));
        bodyMap.put("tokenInfo", JSONObject.wrap(tokenInfoMap));

        //后台通知地址（需设置为【外网】能访问 http https均可），支付成功后银联会自动将异步通知报文post到商户上送的该地址，失败的交易银联不会发送后台通知
        //后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
        //注意:1.需设置为外网能访问，否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码
        //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200，那么银联会间隔一段时间再次发送。总共发送5次，每次的间隔时间为0,1,2,4分钟。
        //    5.后台通知地址如果上送了带有？的参数，例如：http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签，否则将会验签失败
        bodyMap.put("backUrl", SDKConfig.getConfig().getBackUrl()); //后台通知地址,请修改为自定义的通知地址
        bodyMap.put("txnAmt", SDKConfig.getValueFromConfig("txnAmt")); //交易金额
        bodyMap.put("currencyCode", SDKConfig.getValueFromConfig("currencyCode")); //交易币种
        bodyMap.put("customerIp", SDKConfig.getValueFromConfig("customerIp")); //持卡人IP
        bodyMap.put("smallSumFlag", SDKConfig.getValueFromConfig("smallSumFlag")); //小额免验标识
        bodyMap.put("payAppId", SDKConfig.getValueFromConfig("payAppId")); //支付工具代码

        Map<String, Object> riskRateInfo = new HashMap<String, Object>();
        //根据自己实际需求添加规范描述字段
        riskRateInfo.put("sourceIP", SDKConfig.getValueFromConfig("sourceIP"));
        riskRateInfo.put("deviceID", SDKConfig.getValueFromConfig("deviceID"));
        riskRateInfo.put("shippingProvinceCode", SDKConfig.getValueFromConfig("shippingProvinceCode"));
        bodyMap.put("riskRateInfo", riskRateInfo);

        bodyMap.put("maskedCardNo", SDKConfig.getValueFromConfig("maskedCardNo")); //掩码卡号

        Map<String, Object> fundInfoMap = new HashMap<String, Object>();
        fundInfoMap.put("fundComCode", SDKConfig.getValueFromConfig("fundComCode"));
        fundInfoMap.put("fundProCode", SDKConfig.getValueFromConfig("fundProCode"));
        fundInfoMap.put("fundProType", SDKConfig.getValueFromConfig("fundProType"));
        bodyMap.put("fundInfo", JSONObject.wrap(fundInfoMap));

        Map<String, Object> merCtrlInfoMap = new HashMap<String, Object>();
        merCtrlInfoMap.put("noPromotionFlag", SDKConfig.getValueFromConfig("noPromotionFlag"));
        bodyMap.put("merCtrlInfo", JSONObject.wrap(merCtrlInfoMap));

        Map<String, Object> accSplitData = new HashMap<String, Object>();
        accSplitData.put("accSplitType", SDKConfig.getValueFromConfig("accSplitType"));
        //分账类型为2时才出现，
        //15位字母或数字
        accSplitData.put("accSplitRuleId", SDKConfig.getValueFromConfig("accSplitRuleId"));

        //分账类型为1时才出现，
        //accSplitMcht数组，最多支持5个分账对象accSplitMcht
        List<Map<String, Object>> accSplitMchts = new ArrayList<>();
        Map<String, Object> accSplitMcht = new HashMap<String, Object>();
        accSplitMcht.put("accSplitMerId", SDKConfig.getValueFromConfig("accSplitMerId"));
        accSplitMcht.put("accSplitAmt", SDKConfig.getValueFromConfig("accSplitAmt"));
        accSplitMchts.add(accSplitMcht);
        accSplitData.put("accSplitMchts", accSplitMchts);

        bodyMap.put("accSplitData", Acp7Service.base64Encode(JSONObject.wrap(accSplitData).toString()));

        reqData.put("body", JSONObject.wrap(bodyMap).toString());

        /**对请求参数进行签名并发送http post请求，接收同步应答报文**/
        reqMap = Acp7Service.sign(reqData);      //报文中sign,signId的值是在sign方法中获取并自动赋值的，只要证书配置正确即可。
        rspMap = Acp7Service.post(reqMap, demo.url);  //发送请求报文并接受同步应答（默认连接超时时间30秒，读取返回结果超时时间30秒）;这里调用sign之后，调用post之前不能对map中的键值对做任何修改，如果修改会导致验签不通过

        /***应答处理***/
        if (rspMap == null || rspMap.size() == 0) {
            logger.info("post失败，请看下日志文件。");
            return;
        }

        if (!Acp7Service.validate(rspMap)) {
            logger.info("验签失败");
            return;
        }

        Map<String, Object> rspBody;
        try {
            if (rspMap.containsKey("body")) {
                rspBody = DemoUtil.unwarpJson(new JSONObject(rspMap.get("body")));
                String code = (String) rspBody.get("code");
                if (!("0000000000").equals(code)) {
                    String msg = (String) rspBody.get("msg");
                    StringBuffer stringBuffer = new StringBuffer();
                    stringBuffer.append("返回了失败应答：code=" + code + "，msg=" + msg);
                    String subMsg = (String) rspBody.get("subMsg");
                    String subCode = (String) rspBody.get("subCode");
                    if (subCode != null) {
                        stringBuffer.append("，subCode=" + subCode + "，subMsg=" + subMsg);
                    }
                    logger.info(stringBuffer.toString());
                    return;
                }
                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);
                    try {
                        String decodeStr = Acp7Service.rsaDec(decData, decKey);
                        logger.info("解银联加密后的敏感信息的报文：" + decodeStr);
                    } catch (Exception e) {
                        logger.error(e);
                    }
                }

                return;
            }
        } catch (JSONException e) {
            logger.info("应答体解json失败。");//正常不会进这个逻辑
            return;
        }

    }

}
