package com.iformall.pay;

import com.iformall.utils.*;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

public class WxPayment {

	/**
	 * 构建参数
	 * 
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param device_info
	 * @param body
	 * @param detail
	 * @param attach
	 * @param out_trade_no
	 * @param total_fee
	 * @param spbill_create_ip
	 * @param auth_code
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildParasMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
			String device_info, String body, String detail, String attach, String out_trade_no, String total_fee,
			String spbill_create_ip, String auth_code, String paternerKey) {
		Map<String, String> queryParas = new HashMap<String, String>();
		queryParas.put("appid", appid);
		queryParas.put("sub_appid", sub_appid);
		queryParas.put("mch_id", mch_id);
		queryParas.put("sub_mch_id", sub_mch_id);
		queryParas.put("device_info", device_info);
		queryParas.put("nonce_str", String.valueOf(System.currentTimeMillis()));
		queryParas.put("body", body);
		queryParas.put("detail", detail);
		queryParas.put("attach", attach);
		queryParas.put("out_trade_no", out_trade_no);
		queryParas.put("total_fee", total_fee);
		queryParas.put("spbill_create_ip", spbill_create_ip);
		queryParas.put("auth_code", auth_code);
		String sign = WxPayment.createSign(queryParas, paternerKey);
		queryParas.put("sign", sign);
		return queryParas;
	}

	/**
	 * 封装查询请求参数 参考代码
	 * 
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param transaction_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildQueryParasMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
			String transaction_id, String out_trade_no, String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		params.put("transaction_id", transaction_id);
		params.put("out_trade_no", out_trade_no);

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 构建统一下单参数
	 * 
	 * @param appid
	 * @param sub_appid
	 *            否
	 * @param mch_id
	 * @param sub_mch_id
	 *            服务商模式下必须
	 * @param device_info
	 *            否
	 * @param body
	 * @param detail
	 *            否
	 * @param attach
	 *            否
	 * @param out_trade_no
	 * @param total_fee
	 * @param spbill_create_ip
	 * @param paternerKey
	 * @param notify_url
	 * @param openId // JSAPI 必传
	 * @return
	 */
	public static Map<String, String> buildUnifiedOrderParasMap(String appid, String sub_appid, String mch_id,
			String sub_mch_id, String device_info, String body, String detail, String attach, String out_trade_no,
			String total_fee, String spbill_create_ip, String notify_url, String paternerKey,
			String openId) {
		Map<String, String> params = new HashMap<String, String>();
		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		params.put("device_info", device_info);
		params.put("body", body);
		params.put("detail", detail);
		params.put("attach", attach);

		params.put("out_trade_no", out_trade_no);

		params.put("total_fee", total_fee);
		params.put("spbill_create_ip", spbill_create_ip);
		params.put("notify_url", notify_url);
		params.put("trade_type", "JSAPI");
		// params.put("product_id", product_id); // trade_type=NATIVE时（即扫码支付）必传
		params.put("openid", openId);


		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 构建短链接参数
	 * 
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param long_url
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildShortUrlParasMap(String appid, String sub_appid, String mch_id,
			String sub_mch_id, String long_url, String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();
		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		params.put("long_url", long_url);

		return buildSignAfterParasMap(params, paternerKey);

	}

	/**
	 * 小程序统一下单后二次签名
	 *
	 * @param appId
	 * @param timestamp
	 * @param prepay_id
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildWeappSecondSignMap(String appId, String timestamp, String prepay_id, String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();
		Map<String, String> sighMap = MapUtil.getOrderMap();
		sighMap.put("appId", appId);
		sighMap.put("timeStamp", timestamp);
		sighMap.put("package", "prepay_id="+prepay_id);
		//sighMap.put("signType", "MD5");

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 关闭订单
	 *
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildCloseMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
													String out_trade_no, String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		params.put("out_trade_no", out_trade_no);

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 申请退款
	 *
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildRefundMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
													 String transaction_id, String out_trade_no, String out_refund_no,
													 String total_fee, String refund_fee, String refund_desc, String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		params.put("transaction_id", transaction_id);
		params.put("out_trade_no", out_trade_no);
		params.put("out_refund_no", out_refund_no);
		params.put("total_fee", total_fee);
		params.put("refund_fee", refund_fee);
		params.put("refund_desc", refund_desc);

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 查询退款
	 *
	 * @param appid
	 * @param sub_appid
	 * @param mch_id
	 * @param sub_mch_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildRefundQueryMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
														  String transaction_id, String out_trade_no, String out_refund_no, String refund_id,
														  String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		if(!StringUtils.isBlank(refund_id)) {
			params.put("refund_id", refund_id);
		} else if (!StringUtils.isBlank(out_refund_no)) {
			params.put("out_refund_no", out_refund_no);
		} else if (!StringUtils.isBlank(transaction_id)) {
			params.put("transaction_id", transaction_id);
		} else if (!StringUtils.isBlank(out_trade_no)) {
			params.put("out_trade_no", out_trade_no);
		}

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 查询退款
	 *
	 * @param appid
	 * @param mch_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildWeappRefundQueryMap(String appid, String mch_id,
									String transaction_id, String out_trade_no, String out_refund_no, String refund_id,
									String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("mch_id", mch_id);
		if(!StringUtils.isBlank(refund_id)) {
			params.put("refund_id", refund_id);
		} else if (!StringUtils.isBlank(out_refund_no)) {
			params.put("out_refund_no", out_refund_no);
		} else if (!StringUtils.isBlank(transaction_id)) {
			params.put("transaction_id", transaction_id);
		} else if (!StringUtils.isBlank(out_trade_no)) {
			params.put("out_trade_no", out_trade_no);
		}

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 服务商查询退款
	 *
	 * @param appid
	 * @param mch_id
	 * @param out_trade_no
	 * @param paternerKey
	 * @return
	 */
	public static Map<String, String> buildSWeappRefundQueryMap(String appid, String sub_appid, String mch_id, String sub_mch_id,
															   String transaction_id, String out_trade_no, String out_refund_no, String refund_id,
															   String paternerKey) {
		Map<String, String> params = new HashMap<String, String>();

		params.put("appid", appid);
		params.put("sub_appid", sub_appid);
		params.put("mch_id", mch_id);
		params.put("sub_mch_id", sub_mch_id);
		if(!StringUtils.isBlank(refund_id)) {
			params.put("refund_id", refund_id);
		} else if (!StringUtils.isBlank(out_refund_no)) {
			params.put("out_refund_no", out_refund_no);
		} else if (!StringUtils.isBlank(transaction_id)) {
			params.put("transaction_id", transaction_id);
		} else if (!StringUtils.isBlank(out_trade_no)) {
			params.put("out_trade_no", out_trade_no);
		}

		return buildSignAfterParasMap(params, paternerKey);
	}

	/**
	 * 组装签名的字段
	 * 
	 * @param params
	 *            参数
	 * @param urlEncoder
	 *            是否urlEncoder
	 * @return String
	 */
	public static String packageSign(Map<String, String> params, boolean urlEncoder) {
		// 先将参数以其参数名的字典序升序进行排序
		TreeMap<String, String> sortedParams = new TreeMap<String, String>(params);
		// 遍历排序后的字典，将所有参数按"key=value"格式拼接在一起
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for (Entry<String, String> param : sortedParams.entrySet()) {
			String value = param.getValue();
			if (StringUtils.isBlank(value)) {
				continue;
			}
			if (first) {
				first = false;
			} else {
				sb.append("&");
			}
			sb.append(param.getKey()).append("=");
			if (urlEncoder) {
				try {
					value = urlEncode(value);
				} catch (UnsupportedEncodingException e) {
				}
			}
			sb.append(value);
		}
		return sb.toString();
	}

	/**
	 * urlEncode
	 * 
	 * @param src
	 *            微信参数
	 * @return String
	 * @throws UnsupportedEncodingException
	 *             编码错误
	 */
	public static String urlEncode(String src) throws UnsupportedEncodingException {
		return URLEncoder.encode(src,  Charset.forName("UTF-8").name()).replace("+", "%20");
	}

	/**
	 * 构建签名之后的参数
	 * 
	 * @param params
	 * @param paternerKey
	 * @return Map
	 */
	public static Map<String, String> buildSignAfterParasMap(Map<String, String> params, String paternerKey) {
		params.put("nonce_str", Utility.generateUUID().replace("-", ""));
		String sign = WxPayment.createSign(params, paternerKey);
		params.put("sign", sign);
		return params;
	}

	/**
	 * 构建签名之后的参数
	 *
	 * @param params
	 * @param paternerKey
	 * @return Map
	 */
	public static Map<String, String> buildSignAfterParasMapForHMAC(Map<String, String> params, String paternerKey) {
		params.put("nonce_str", Utility.generateUUID().replace("-", ""));
		String sign = WxPayment.createSignHMAC(params, paternerKey);
		params.put("sign", sign);
		return params;
	}

	/**
	 * 生成签名
	 * 
	 * @param params
	 *            参数
	 * @param partnerKey
	 *            支付密钥
	 * @return sign
	 */
	public static String createSign(Map<String, String> params, String partnerKey) {
		// 生成签名前先去除sign
		params.remove("sign");
		String stringA = packageSign(params, false);
		String stringSignTemp = stringA + "&key=" + partnerKey;
		return HashUtil.md5(stringSignTemp).toUpperCase();
	}

	/**
	 * 生成签名
	 *
	 * @param params
	 *            参数
	 * @param partnerKey
	 *            支付密钥
	 * @return sign
	 */
	public static String createSignHMAC(Map<String, String> params, String partnerKey) {
		// 生成签名前先去除sign
		params.remove("sign");
		String stringA = packageSign(params, false);
		String stringSignTemp = stringA + "&key=" + partnerKey;
		return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, partnerKey).hmacHex(stringSignTemp).toUpperCase();
	}

	/**
	 * 支付异步通知时校验sign
	 * 
	 * @param params
	 *            参数
	 * @param paternerKey
	 *            支付密钥
	 * @return {boolean}
	 */
	public static boolean verifyNotify(Map<String, String> params, String paternerKey) {
		String sign = params.get("sign");
		if (sign == null)
			return true;
		String localSign = WxPayment.createSign(params, paternerKey);
		return sign.equals(localSign);
	}

	/**
	 * 支付异步通知时校验sign
	 *
	 * @param params
	 *            参数
	 * @param paternerKey
	 *            支付密钥
	 * @return {boolean}
	 */
	public static boolean verifyNotifyHMAC(Map<String, String> params, String paternerKey) {
		String sign = params.get("sign");
		if (sign == null)
			return true;
		String localSign = WxPayment.createSignHMAC(params, paternerKey);
		return sign.equals(localSign);
	}

	/**
	 * 判断接口返回的code是否是SUCCESS
	 * 
	 * @param return_code、result_code
	 * @return
	 */
	public static boolean codeIsOK(String return_code) {
		return StringUtils.isNotBlank(return_code) && "SUCCESS".equals(return_code);
	}

	/**
	 * 微信下单map to xml
	 * 
	 * @param params
	 *            参数
	 * @return String
	 */
	public static String toXml(Map<String, String> params) {
		StringBuilder xml = new StringBuilder();
		xml.append("<xml>");
		for (Entry<String, String> entry : params.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			// 略过空值
			if (StringUtils.isBlank(value))
				continue;
			xml.append("<").append(key).append(">");
			xml.append(entry.getValue());
			xml.append("</").append(key).append(">");
		}
		xml.append("</xml>");
		return xml.toString();
	}

	/**
	 * 针对支付的xml，没有嵌套节点的简单处理
	 * 
	 * @param xmlStr
	 *            xml字符串
	 * @return map集合
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, String> xmlToMap(String xmlStr) {
		return XmlUtil.parseXml2Map(xmlStr);
	}

	/**
	 * 替换url中的参数
	 * 
	 * @param str
	 * @param regex
	 * @param args
	 * @return
	 */
	public static String replace(String str, String regex, String... args) {
		int length = args.length;
		for (int i = 0; i < length; i++) {
			str = str.replaceFirst(regex, args[i]);
		}
		return str;
	}

    private static void getSanboxKeyTest() {
        Map map = MapUtil.getOrderMap();
        map.put("mch_id", "1511925291");
        // map.put("sub_mch_id", "1513691501");
        map.put("nonce_str", RandomUtils.getStr(32));
        String sign = WxPayment.createSign(map, "XHZfpVA0NzoXgLEjsujctUTcyj8Zur2C");
        map.put("sign", sign);
        String response = WxPay.sanboxSignGet(map);
        System.out.println(response);

        /*
		<xml>
          <return_code><![CDATA[SUCCESS]]></return_code>
          <return_msg><![CDATA[ok]]></return_msg>
          <sandbox_signkey><![CDATA[7e9d244da17632cdf608e04564a628da]]></sandbox_signkey>
        </xml>
		 */
    }


    private static void getDownloadBillTest(String signKey) {
        Map map = MapUtil.getOrderMap();
        map.put("appid", "wxed2f44705544b892");
        map.put("mch_id", "1511925291");
        map.put("nonce_str", RandomUtils.getStr(32));
        map.put("bill_date", "20190524");
        map.put("bill_type", "ALL");
        String sign = WxPayment.createSign(map, signKey);
        map.put("sign", sign);
        String response = WxPay.downloadBill(map);
        System.out.println(response);
    }

    private static void microPaySanbox(String signKey, String outTradeNo) {
        Map map = MapUtil.getOrderMap();
        map.put("appid", "wxed2f44705544b892");
        map.put("mch_id", "1511925291");
        map.put("nonce_str", RandomUtils.getStr(32));
        map.put("body", "image形象店-深圳腾大- QQ公仔");
        map.put("out_trade_no", outTradeNo);
        map.put("total_fee", "502");
        map.put("spbill_create_ip", "172.16.115.182");
        map.put("auth_code", "134659695358765866");
        String sign = WxPayment.createSign(map, signKey);
        map.put("sign", sign);
        String response = WxPay.micropay(map);
        System.out.println("micropay");
        System.out.println(response);
    }

    private static void queryPaySanbox(String signKey, String outTradeNo) {
        Map map = MapUtil.getOrderMap();
        map.put("appid", "wxed2f44705544b892");
        map.put("mch_id", "1511925291");
        map.put("out_trade_no", outTradeNo);
        map.put("nonce_str", RandomUtils.getStr(32));
        String sign = WxPayment.createSign(map, signKey);
        map.put("sign", sign);
        String response = WxPay.orderQuery(map);
        System.out.println("micropayQuery");
        System.out.println(response);
    }

    private static void refundSanbox(String signKey, String outTradeNo, String outRefundNo) {
        Map map = MapUtil.getOrderMap();
        map.put("appid", "wxed2f44705544b892");
        map.put("mch_id", "1511925291");
        map.put("nonce_str", RandomUtils.getStr(32));
        map.put("out_trade_no", outTradeNo);
        map.put("out_refund_no", outRefundNo);
        map.put("total_fee", "502");
        map.put("refund_fee", "501");
        String sign = WxPayment.createSign(map, signKey);
        map.put("sign", sign);
        String response = WxPay.orderRefund(map, "/opt/iformall/service/apiclient_cert.p12", "1511925291");
        System.out.println("refund");
        System.out.println(response);
    }

    private static void refundQuerySanbox(String signKey, String outTradeNo, String outRefundNo) {
        Map map = MapUtil.getOrderMap();
        map.put("appid", "wxed2f44705544b892");
        map.put("mch_id", "1511925291");
        map.put("nonce_str", RandomUtils.getStr(32));
        map.put("out_trade_no", outTradeNo);
        map.put("out_refund_no", outRefundNo);
        String sign = WxPayment.createSign(map, signKey);
        map.put("sign", sign);
        String response = WxPay.orderRefundQuery(map);
        System.out.println("refundQuery");
        System.out.println(response);
    }

	public static void main(String[] args) {
	    String signKey = "7e9d244da17632cdf608e04564a628da";

        String outTradeNo = "1459833232543346";
        String outRefundNo = "143446547";
        microPaySanbox(signKey, outTradeNo);
        queryPaySanbox(signKey, outTradeNo);
        refundSanbox(signKey, outTradeNo, outRefundNo);
        refundQuerySanbox(signKey, outTradeNo, outRefundNo);
	}

}
