package com.wechat.pay;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.library.config.ConfigCon;
import com.library.util.ClientInternet;
import com.library.util.Tools;

/**
 * 微信公众号支付管理类
 * @author Administrator
 *
 */
public class WeChatManager {
	/** 记录时间的毫秒数，用作获取access_token */
	public static long Time_Access_Token=0;
	
	/**记录时间的毫秒数，用作获取jsapi_ticket*/
	public static long Time_jsapi_ticket=0;
	
	/**
	 * Access_Token的值
	 */
	private static String Access_Token="";
	
	/**
	 * 页面支付使用权限的时候所需要用的Jsapi_Ticket
	 */
	public static String Jsapi_Ticket="";
	
	/**
	 * 获得access_token
	 * 例子：{"access_token":"v6_-DN1HhXuQATEmX1RnCbj_vWyPmvGdRHzUqm9A0dKZUqQcPfwP_htMddUANclCRBLpK6IOdQiR5unFBvilRjW_Di6bDrLg4QhXeO4xPywPDEdAAAHVW","expires_in":7200}
	 */
	public static String GetAccess_token(){
		long time=System.currentTimeMillis();
		//判断是否距离上一次获得超过生效时间
		if(time>Time_Access_Token){
			String address="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ConfigCon.WeChat_Appid+"&secret="+ConfigCon.WeChat_AppSecret;
			String json=ClientInternet.OpearConnect(address, null, "");
			Token t=Tools.gson.fromJson(json, Token.class);
			Access_Token=t.access_token;
			Time_Access_Token=time+t.expires_in*1000;//保存时间记录
			System.out.println("系统通知：获取一次Access_Token！");
			System.out.println("系统通知："+Access_Token);
			return t.access_token;
		}else{
			return Access_Token;
		}
	}
	
	/**
	 * 判断是否获取token失效
	 */
	public static boolean isTokenInvalid(String res){
		/**
		 * -1	系统繁忙，此时请开发者稍候再试
		 0	请求成功
		 40001	AppSecret错误或者AppSecret不属于这个公众号，请开发者确认AppSecret的正确性
		 40002	请确保grant_type字段值为client_credential
		 40164	调用接口的IP地址不在白名单中，请在接口IP白名单中进行设置。（小程序及小游戏调用不要求IP地址在白名单内。）
		 */
		JsonElement resElement =  new JsonParser().parse(res).getAsJsonObject().get("errcode");
		if(resElement!=null && resElement.getAsInt()==40001){
			MustGetAccess_token();
			//无效的
			return false;
		}
		return true;
	}


	/**
	 * 在access_token失效的时候，强行重新获取
	 * 例子：{"access_token":"v6_-DN1HhXuQATEmX1RnCbj_vWyPmvGdRHzUqm9A0dKZUqQcPfwP_htMddUANclCRBLpK6IOdQiR5unFBvilRjW_Di6bDrLg4QhXeO4xPywPDEdAAAHVW","expires_in":7200}
	 */
	public static void MustGetAccess_token(){
		long time=System.currentTimeMillis();
		String address="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ConfigCon.WeChat_Appid+"&secret="+ConfigCon.WeChat_AppSecret;
		String json=ClientInternet.OpearConnect(address, null, "");
		Token t=Tools.gson.fromJson(json, Token.class);
		Access_Token=t.access_token;
		Time_Access_Token=time+t.expires_in*1000;//保存时间记录
		System.out.println("系统通知：强行重新获取Access_Token！");
		System.out.println("系统通知："+Access_Token);

	}
	
	/**
	 * 长转短二维码连接
	 */
	public static String ShortUrl(String url){
		String post="{\"action\":\"long2short\",\"long_url\":\""+url+"\"}";
		String address="https://api.weixin.qq.com/cgi-bin/shorturl?access_token="+GetAccess_token();
		String json=ClientInternet.OpearConnect(address, null, post);
		//{"errcode":0,"errmsg":"ok","short_url":"http:\/\/w.url.cn\/s\/ATdm0Jn"}
		ShortUrl shortUrl=Tools.gson.fromJson(json, ShortUrl.class);
		return shortUrl.short_url;
	}
	
	/**
	 * 用户授权后通过Code获取用户的openID
	 */
	public static String GetOPENID(String code){
		String address="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ConfigCon.WeChat_Appid+"&secret="+ConfigCon.WeChat_AppSecret+"&code="+code+"&grant_type=authorization_code";
		String res=ClientInternet.OpearConnect(address, null, "");
		System.out.println("获得用户Openid的Json数据："+res);
		Openid openid=Tools.gson.fromJson(res, Openid.class);
		return openid.openid;
	}
	
	/**
	 * 获得jsapi_ticket
	 * {"errcode":0,"errmsg":"ok","ticket":"kgt8ON7yVITDhtdwci0qedpqYX2X2ceQdAG8fUyTui_0tR7TUA-w6hjQuWVfjcptgeAhzoFpvcb2n8yQJDlVhQ","expires_in":7200}
	 * @return 
	 */
	public static String GetJsapi_Ticket(){
		long time=System.currentTimeMillis();
		//判断是否距离上一次获得超过生效时间
		if(time>Time_jsapi_ticket){
			String ACCESS_TOKEN=GetAccess_token();
			String address="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ACCESS_TOKEN+"&type=jsapi";
			String json=ClientInternet.OpearConnect(address, null, "");
			jsapi_ticket jsa=Tools.gson.fromJson(json, jsapi_ticket.class);
			Jsapi_Ticket=jsa.ticket;
			Time_jsapi_ticket=time+jsa.expires_in*1000;
			System.out.println("系统通知：获取一次Jsapi_Ticket！");
			System.out.println("系统通知："+Jsapi_Ticket);
			return jsa.ticket;
		}else{
			return Jsapi_Ticket;
		}
	}
	
	
	/**
	 * 通过网页授权的方式获得用户信息(网页授权)
	 */
	public static UserInfo GetUserInfoForHtml(String code){
		String address="https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ConfigCon.WeChat_Appid+"&secret="+ConfigCon.WeChat_AppSecret+"&code="+code+"&grant_type=authorization_code";
		String res=ClientInternet.OpearConnect(address, null, "");
//		System.out.println("第一个："+res);
		AccessTokenForHtml acctoken=Tools.gson.fromJson(res, AccessTokenForHtml.class);
		//获得用户信息
		String add="https://api.weixin.qq.com/sns/userinfo?access_token="+acctoken.access_token+"&openid="+acctoken.openid+"&lang=zh_CN";
		String res2=ClientInternet.OpearConnect(add, null, "");
//		System.out.println("第二个："+res2);
		UserInfo unionID=Tools.gson.fromJson(res2, UserInfo.class);
		return unionID;
	}
	
	
	/**
	 * 通过普通方式openid获得用户的基本信息（非网页授权）
	 *  {"subscribe":1,"openid":"o5ZsSuBd4skx6-WWOptFnV_yAh0k","nickname":"自由人。","sex":2,"language":"zh_CN","city":"","province":"法兰克福","country":"德国","headimgurl":"http:\/\/wx.qlogo.cn\/mmopen\/PiajxSqBRaELpXUTV9kXUvRux4cqlkFARaibpqp3yfldea0EYaSqibOZrKica06NSsUxbyxEVV5bA5nzjWBIibAL4mQ\/0","subscribe_time":1441625404,"remark":"","groupid":0,"tagid_list":[]}
	 */
	public static UserUnionID GetUserInfo(String openid){
		String address="https://api.weixin.qq.com/cgi-bin/user/info?access_token="+GetAccess_token()+"&openid="+openid+"&lang=zh_CN";
		String res=ClientInternet.OpearConnect(address, null, "");
		UserUnionID unionID=Tools.gson.fromJson(res, UserUnionID.class);
		return unionID;
	}
	
	/**
	 * 调用公众号下单接口，获得对应的唤起支付数据内容
	 */
	public static WeChatPayAttribute ConnectCreateOrder(String post,String notify_url){
		WeChatPayAttribute jsp=new WeChatPayAttribute();
		String res=ClientInternet.OpearConnect(ConfigCon.AddressCreateOrder, null, post);
		System.out.println("微信响应结果是：\n"+res);
		
		/**
		 * 解析微信支付下单后返回来的XML
		 */
		org.dom4j.Document document = null;
		try {
			document = DocumentHelper.parseText(res);
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		org.dom4j.Element root = document.getRootElement();
		String nonce_str = root.element("nonce_str").getText();//随机字符串
		String signtwo = root.element("sign").getText();
		String prepay_id = root.element("prepay_id").getText();
		System.out.println("得到的字符串："+nonce_str+"-"+signtwo+"-"+prepay_id);
		long timeStamp=System.currentTimeMillis()/1000;
		jsp.setNonce_str(nonce_str);
		jsp.setPrepay_id(prepay_id);
		jsp.setTimeStamp(timeStamp);
		
		/**
		 * 生成唤起支付的签名
		 * 签名的参数有appId, timeStamp, nonceStr, package, signType
		 */
		SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();  
		parameters.put("appId", ConfigCon.WeChat_Appid);  
	    parameters.put("timeStamp", timeStamp); 
	    parameters.put("nonceStr", nonce_str);  
	    parameters.put("package", "prepay_id="+prepay_id);  
	    parameters.put("signType", "MD5");
	    String mySign=createSign(ConfigCon.WeChat_APIKey,"UTF-8",parameters);
	    System.out.println("生成下单的sign是："+mySign);
	    jsp.setSign(mySign);
	    
	    /**
	     * JS-SDK使用权限签名算法
	     */
	    SortedMap<Object,Object> parameter2 = new TreeMap<Object,Object>();  
		parameter2.put("noncestr", nonce_str);  
	    parameter2.put("timestamp", timeStamp); 
	    parameter2.put("jsapi_ticket", GetJsapi_Ticket());  
	    parameter2.put("url", notify_url);
	    String sign2=createSign2(parameter2);
	    System.out.println("JS-SDK使用权限签名sign2："+sign2);
	    jsp.setSign2(sign2);
	    return jsp;
	}
	
	/**
	 * 生成订单的XML
	 * 参数：1客户端IP地址，2客户端开放ID，3商品或支付单简要描述,4商户系统内部的订单号,5订单总金额，单位为分
	 */
	public static String GetOrderXML(String spbill_create_ip,String openid,String body,String out_trade_no,String total_fee,String notify_url){
		String appid=ConfigCon.WeChat_Appid;//微信分配的公众账号ID
		String mch_id=ConfigCon.WeChat_Mch_id;//微信支付分配的商户号
		String nonce_str="suiji"+System.currentTimeMillis();//随机字符串，不长于32位
		//String body="lexiangdianjing";//商品或支付单简要描述
		//String out_trade_no="dingdan"+System.currentTimeMillis();//商户系统内部的订单号,32个字符内
		//String total_fee="10";//订单总金额，单位为分
		String trade_type="JSAPI";//取值如下：JSAPI，NATIVE，APP，
		//String notify_url=ConfigCon.WeChat_Notify_url;//接收微信支付异步通知回调地址，通知url必须为直接可访问的url，不能携带参数
		
		
		// 创建根节点 并设置它的属性 ;
	    Element root = new Element("xml");
	    root.addContent(new Element("appid").setText(appid));//微信分配的公众账号ID
	    root.addContent(new Element("mch_id").setText(mch_id));//微信支付分配的商户号
	    root.addContent(new Element("nonce_str").setText(nonce_str));//随机字符串，不长于32位
	    root.addContent(new Element("body").setText(body));//商品或支付单简要描述
	    root.addContent(new Element("out_trade_no").setText(out_trade_no));//商户系统内部的订单号,32个字符内
	    root.addContent(new Element("total_fee").setText(total_fee));//订单总金额，单位为分
	    root.addContent(new Element("spbill_create_ip").setText(spbill_create_ip));//APP和网页支付提交用户端ip，Native支付填调用微信支付API的机器IP
	    root.addContent(new Element("notify_url").setText(notify_url));//接收微信支付异步通知回调地址，通知url必须为直接可访问的url，不能携带参数
	    root.addContent(new Element("trade_type").setText(trade_type));//取值如下：JSAPI，NATIVE，APP，
	    root.addContent(new Element("openid").setText(openid));//用户在商户appid下的唯一标识。
	    
	    
	    SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();  
	    parameters.put("appid", appid);  
	    parameters.put("mch_id", mch_id); 
	    parameters.put("nonce_str", nonce_str);  
	    parameters.put("body", body);  
	    parameters.put("out_trade_no", out_trade_no); 
	    parameters.put("total_fee", total_fee);  
	    parameters.put("spbill_create_ip", spbill_create_ip);  
	    parameters.put("notify_url", notify_url);
	    parameters.put("trade_type", trade_type);
	    parameters.put("openid", openid);
	    
	    String mySign = createSign(ConfigCon.WeChat_APIKey,"UTF-8",parameters);
	    root.addContent(new Element("sign").setText(mySign));//签名，详见签名生成算法
	    Document Doc = new Document(root);
	    Format format = Format.getPrettyFormat();  
	    XMLOutputter XMLOut = new XMLOutputter(format);  
	    System.out.println("生成订单的XML：\n"+XMLOut.outputString(Doc));
		return XMLOut.outputString(Doc);
	}
	
	/** 
	 * 生成下单签名
	 */
	public static String createSign(String Key,String characterEncoding,SortedMap<Object,Object> parameters){  
	    StringBuffer sb = new StringBuffer();  
	    Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序（升序）  
	    Iterator it = es.iterator();  
	    while(it.hasNext()) {  
	    	
	        Map.Entry entry = (Map.Entry)it.next();
	        String k = (String)entry.getKey();  
	        Object v = entry.getValue();  
	        if(null != v && !"".equals(v)   
	                && !"sign".equals(k) && !"key".equals(k)) {  
	            sb.append(k + "=" + v + "&");  
	        }  
	    }  
	    sb.append("key=" + Key);  
	    String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();  
	    return sign;  
	}  

	/** 
	 * JS-SDK使用权限签名算法
	 */
	public static String createSign2(SortedMap<Object,Object> parameters){  
	    StringBuffer sb = new StringBuffer();  
	    Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序（升序）  
	    Iterator it = es.iterator();  
	    while(it.hasNext()) {  
	    	
	        Map.Entry entry = (Map.Entry)it.next();
	        String k = (String)entry.getKey();  
	        Object v = entry.getValue();  
	        if(null != v && !"".equals(v)   
	                && !"sign".equals(k) && !"key".equals(k)) {  
	            sb.append(k + "=" + v + "&");  
	        }  
	    }  
	    String sb2=sb.substring(0, sb.length()-1);
	    return DigestUtils.shaHex(sb2);  
	}  
	
	class jsapi_ticket{
		public String errcode;
		public String errmsg;
		public String ticket;
		public long expires_in;
	}
	
	class Openid{
		public String access_token;
		public String expires_in;
		public String refresh_token;
		public String openid;
		public String scope;
	}
	
	//获得网页授权的access_token
	class AccessTokenForHtml{
		public String access_token;
		public String expires_in;//access_token接口调用凭证超时时间，单位（秒）
		public String refresh_token;
		public String openid;
		public String scope;
	}
	
	//获得access_token
	class Token{
		public String access_token;
		public long expires_in;
	}
	//网页授权后获取的用户信息
	public class UserInfo{
		public String openid;
		public String nickname;
		public String sex;
		public String province;
		public String city;
		public String country;
		public String headimgurl;
		public String[] privilege;
		public String unionid;
	}
	
	//获得用户的信息
	public class UserUnionID{
		public String subscribe;
		public String openid;
		public String nickname;
		public String sex;
		public String language;
		public String city;
		public String province;
		public String country;
		public String headimgurl;
		public String subscribe_time;
		public String unionid;
		public String remark;
		public String groupid;
	}
	
	
	//简化二维码的内容
	class ShortUrl{
		public String errcode;
		public String errmsg;
		public String short_url;
	}
	
	
}
