package com.yunniu.farming.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;

import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.*;

/**
 * APP内调起微信支付工具类
 */
public class WxPayAppUtils {
	

	private static int socketTimeout = 10000;// 连接超时时间，默认10秒
	private static int connectTimeout = 30000;// 传输超时时间，默认30秒
	private static RequestConfig requestConfig;// 请求器的配置
	private static CloseableHttpClient httpClient;// HTTP请求器
	
	//微信支付参数列表
	String appId = "微信开放平台申请的移动端的appid";
	// 商户号
	String partnerId = "微信商户平台的商户号";
	// key为商户平台设置的密钥key
	String WXKeHukey = "商户平台设置的密钥";
	//扩展字段，暂填写固定值Sign=WXPay，java无法使用package关键字作为变量名所以用packageValue替代
	public static final String packageValue = "Sign=WXPay";
	// 本机的ip地址
	String spbill_create_ip = "本机的ip地址";
	// 支付回调地址
	String notify_url = "支付回调地址";
	// 调起支付方式
	public static final String trade_type = "APP";
	// 签名方式，固定值
	public static final String SIGNTYPE = "MD5";
	// 第一次发起支付请求的接口地址
	public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// 退款URL
	public static final String tk_url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
	// 查询订单
	public static final String weixinPayQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
	
	public static Map<String, Object> wxPay(
					HttpServletRequest request,
					String appid,
					String mch_id,
					String key,
					String notify_url,
					String productname,
					String out_trade_no,
					String total_fee,
					String attach)throws Exception {
		
		// 生成的随机字符串
		String nonce_str = getRandomStringByLength(32);
		// 商品名称
		String body = productname;
		// 获取客户端的ip地址
		String spbill_create_ip = getIpAddr(request);
		
		// 组装参数，用户生成统一下单接口的签名
		Map<String, String> packageParams = new HashMap<String, String>();
		packageParams.put("appid", appid);
		packageParams.put("mch_id", mch_id);
		packageParams.put("sub_mch_id", mch_id);
		packageParams.put("nonce_str", nonce_str);
		packageParams.put("body", body);
		packageParams.put("out_trade_no", out_trade_no);// 商户订单号
		packageParams.put("total_fee", String.valueOf(total_fee));// 支付金额，这边需要转成字符串类型，否则后面的签名会失败
		packageParams.put("spbill_create_ip", spbill_create_ip);
		packageParams.put("notify_url", notify_url);// 支付成功后的回调地址
		packageParams.put("trade_type", trade_type);// 交易类型(JSAPI--JSAPI支付（或小程序支付）、NATIVE--Native支付、APP--app支付，MWEB--H5支付，不同trade_type决定了调起支付的方式，请根据支付产品正确上传)
		packageParams.put("attach", attach);// 附加数据，主要用于存放订单id，用于支付成功后回写数据状态
		
		String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
		// MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
		String mysign = PayUtil.sign(prestr, key, "UTF-8").toUpperCase();
		
		// 拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
		String xml = "<xml>"
				+ "<appid>" + appid + "</appid>"
				+ "<body><![CDATA[" + body + "]]></body>"
				+ "<attach>"+ attach + "</attach>" 
				+ "<mch_id>" + mch_id + "</mch_id>"
				+ "<sub_mch_id>" + mch_id + "</sub_mch_id>"
				+ "<nonce_str>" + nonce_str + "</nonce_str>"
				+ "<notify_url>" + notify_url + "</notify_url>"
				+ "<out_trade_no>" + out_trade_no + "</out_trade_no>" 
				+ "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" 
				+ "<total_fee>" + String.valueOf(total_fee) + "</total_fee>"
				+ "<trade_type>" + trade_type + "</trade_type>" 
				+ "<sign>" + mysign + "</sign>" 
		+ "</xml>";
		
		//System.out.println("调试模式_统一下单接口 请求XML数据：" + xml);
		// 调用统一下单接口，并接受返回的结果
		//String result = PayUtil.httpRequest(pay_url, "POST", xml);
		
		//FRICE TODO 此处是坑xxxxxxxxxxxxxxxxx！！！向腾讯发送报文前进行转码，不然发送不成功
		String result = PayUtil.httpRequest(pay_url, "POST", new String(xml.getBytes("utf-8"), "ISO8859-1"));
		//System.out.println("调试模式_统一下单接口 返回XML数据：" + result);
		
		// 将解析结果存储在HashMap中
		Map map = PayUtil.doXMLParse(result);
		String return_code = (String) map.get("return_code");// 返回状态码
		
		Map<String, Object> response = new HashMap<String, Object>();// 返回给小程序端需要的参数
		
		//返回给移动端需要的参数
		if (return_code.equals("SUCCESS")) {
			
			String prepay_id = (String) map.get("prepay_id");// 返回的预付单信息
			Long timeStamp = System.currentTimeMillis() / 1000;
			
			response.put("appid", appid);
			response.put("partnerid", mch_id);
			response.put("prepayid", prepay_id);
			response.put("noncestr", nonce_str);
			response.put("package", packageValue);
			response.put("timestamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
			
			// 拼接签名需要的参数
//				String stringSignTemp = "appId=" + appid + "&nonceStr="
//						+ nonce_str + "&package=" + packageValue
//						+ "&signType=MD5&timeStamp=" + timeStamp;
			
			String stringSignTemp = PayUtil.createLinkStringObject(response); // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
			
			// 再次签名，这个签名用于小程序端调用wx.requesetPayment方法
			String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase();
			
			response.put("sign", paySign);
			
			response.put("result_code", "01");// 预支付请求结果:ok
			
		} else {
			response.put("result_code", "02");// 预支付请求结果
			response.put("err_desc", map.get("return_msg"));// 预支付请求结果
		}

		return response;
	}
	
	/**
	 * 微信退款
	 * @param appid
	 * @param mch_id
	 * @param out_trade_no 订单金额
	 * @param out_refund_no 退款金额
	 * @param total_fee 订单编号
	 * @param refund_fee 退款编号
	 * @param key API密钥
	 * @return
	 */
	public static Map<String, Object> applyForRefund(String appid, String mch_id,
                                                     String out_trade_no, String out_refund_no, String total_fee, String refund_fee, String key,
                                                     String notify_url) {

		// 1.0 拼凑微信退款需要的参数
		String nonce_str = WxPayAppUtils.getRandomStringByLength(32); // 生成随机数
		
		// 2.0 生成map集合
		SortedMap<String, String> packageParams = new TreeMap<String, String>();
		packageParams.put("appid", appid); // 微信公众号的appid
		packageParams.put("mch_id", mch_id); // 商务号
		packageParams.put("nonce_str", nonce_str); // 随机生成后数字，保证安全性
		packageParams.put("out_trade_no", out_trade_no);
		packageParams.put("out_refund_no", out_refund_no);
		packageParams.put("total_fee", total_fee);
		packageParams.put("refund_fee", refund_fee);
		packageParams.put("notify_url", notify_url);
		String prestr = PayUtil.createLinkString(packageParams);

		try {
			// 3.0 利用上面的参数，先去生成自己的签名
			String sign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();

			// 4.0 将签名再放回map中，它也是一个参数
			packageParams.put("sign", sign);

			// 5.0将当前的map结合转化成xml格式
			String xml = "<xml>" + "<appid>" + appid + "</appid>" + "<mch_id>"
					+ mch_id + "</mch_id>" + "<nonce_str>" + nonce_str
					+ "</nonce_str>" + "<out_refund_no>" + out_refund_no
					+ "</out_refund_no>" + "<refund_fee>"
					+ String.valueOf(refund_fee) + "</refund_fee>"
					+ "<total_fee>" + String.valueOf(total_fee)
					+ "</total_fee>" + "<out_trade_no>" + out_trade_no
					+ "</out_trade_no>" + "<sign>" + sign + "</sign>"
					+ "<notify_url>" + notify_url + "</notify_url>"
					+ "</xml>";

			// 6.0获取需要发送的url地址
			String wxUrl = tk_url; // 获取退款的api接口

			// //System.out.println("发送前的xml为：" + xml);

			// 7,向微信发送请求转账请求
			String returnXml = WxPayAppUtils.postData(wxUrl, xml, mch_id, "/apiclient_cert.p12");
			
			// //System.out.println("发送后的xml为: " + returnXml);
			
			// 8，将微信返回的xml结果转成map格式
			Map map = PayUtil.doXMLParse(returnXml);
			
			String return_code = (String) map.get("return_code");// 返回状态码
			
			Map<String, Object> response = new HashMap<String, Object>();// 返回给小程序端需要的参数
			
			if (return_code.equals("SUCCESS")) {
				String prepay_id = (String) map.get("prepay_id");// 返回的预付单信息
				response.put("result_code", "01");// 预支付请求结果
				response.put("nonceStr", nonce_str);
				response.put("signType", "MD5");// 加密方式
				response.put("package_str", "prepay_id=" + prepay_id);
				Long timeStamp = System.currentTimeMillis() / 1000;
				response.put("timeStamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
				// 拼接签名需要的参数
				String stringSignTemp = "appId=" + appid + "&nonceStr="
						+ nonce_str + "&package=prepay_id=" + prepay_id
						+ "&signType=MD5&timeStamp=" + timeStamp;
				// 再次签名，这个签名用于小程序端调用wx.requesetPayment方法
				String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase();
				response.put("paySign", paySign);
			} else {
				response.put("result_code", "02");// 预支付请求结果
				response.put("err_desc", xml);// 预支付请求结果
			}

			return response;

		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	/**
	 * StringUtils工具类方法 获取一定长度的随机字符串，范围0-9，a-z
	 * 
	 * @param length
	 *            ：指定字符串长度
	 * @return 一定长度的随机字符串
	 */
	public static String getRandomStringByLength(int length) {
		String base = "abcdefghijklmnopqrstuvwxyz0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		return sb.toString();
	}

	/**
	 * IpUtils工具类方法 获取真实的ip地址
	 * 
	 * @param request
	 * @return
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("X-Forwarded-For");
		if (StringHelper.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			// 多次反向代理后会有多个ip值，第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (StringHelper.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}

	/**
	 * 通过Https往API post xml数据
	 * 
	 * @param url
	 *            API地址
	 * @param xmlObj
	 *            要提交的XML数据对象
	 * @param mchId
	 *            商户ID
	 * @param certPath
	 *            证书位置
	 * @return
	 */
	public static String postData(String url, String xmlObj, String mchId,
                                  String certPath) {
		// 加载证书
		try {
			initCert(mchId, certPath);
		} catch (Exception e) {
			e.printStackTrace();
		}
		String result = null;
		HttpPost httpPost = new HttpPost(url);
		// 得指明使用UTF-8编码，否则到API服务器XML的中文不能被成功识别
		StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
		httpPost.addHeader("Content-Type", "text/xml");
		httpPost.setEntity(postEntity);
		// 根据默认超时限制初始化requestConfig
		requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
				.setConnectTimeout(connectTimeout).build();
		// 设置请求器的配置
		httpPost.setConfig(requestConfig);
		try {
			HttpResponse response = null;
			try {
				response = httpClient.execute(httpPost);
			} catch (IOException e) {
				e.printStackTrace();
			}
			HttpEntity entity = response.getEntity();
			try {
				result = EntityUtils.toString(entity, "UTF-8");
			} catch (IOException e) {
				e.printStackTrace();
			}
		} finally {
			httpPost.abort();
		}
		return result;
	}

	/**
	 * 加载证书
	 * 
	 * @param mchId
	 *            商户ID
	 * @param certPath
	 *            证书位置
	 * @throws Exception
	 */
	private static void initCert(String mchId, String certPath)
			throws Exception {
		// 证书密码，默认为商户ID
		String key = mchId;
		// 证书的路径
		String path = certPath;
		// 指定读取证书格式为PKCS12
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		// 读取本机存放的PKCS12证书文件
		// ClassPathResource cp = new ClassPathResource(AuthUtil.CERTPATH);
		ClassPathResource cp = new ClassPathResource(path);
		InputStream instream = cp.getInputStream();
		try {
			// 指定PKCS12的密码(商户ID)
			keyStore.load(instream, key.toCharArray());
		} finally {
			instream.close();
		}
		SSLContext sslcontext = SSLContexts.custom()
				.loadKeyMaterial(keyStore, key.toCharArray()).build();
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
				sslcontext);
		httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
	}
}
